diff options
Diffstat (limited to 'src')
60 files changed, 1132 insertions, 200 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 8985e4367..d0e506689 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -29,7 +29,7 @@ if ($ENV{CI}) if (BUILD_VERSION) # This leaves a trailing space on the last word, but we actually want that # because of how it's styled in the title bar. - set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ") + set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ") else() set(BUILD_FULLNAME "") endif() diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index 589ae5cbf..5b63f9e81 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) { return 0; } +std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) { + std::vector<u8> out(str.size() / 2); + if (little_endian) { + for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2) + out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + } else { + for (std::size_t i = 0; i < str.size(); i += 2) + out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + } + return out; +} + +std::string HexVectorToString(const std::vector<u8>& vector, bool upper) { + std::string out; + for (u8 c : vector) + out += fmt::format(upper ? "{:02X}" : "{:02x}", c); + return out; +} + std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { if (len != 32) { LOG_ERROR(Common, diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 863a5ccd9..68f003cb6 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <string> +#include <vector> #include <fmt/format.h> #include "common/common_types.h" @@ -14,6 +15,8 @@ namespace Common { u8 ToHexNibble(char c1); +std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); + template <std::size_t Size, bool le = false> std::array<u8, Size> HexStringToArray(std::string_view str) { std::array<u8, Size> out{}; @@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) { return out; } +std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true); + template <std::size_t Size> std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { std::string out; diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp index 9526ca0c6..b916b4866 100644 --- a/src/common/param_package.cpp +++ b/src/common/param_package.cpp @@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0"; constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1"; constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2"; +/// A placeholder for empty param packages to avoid empty strings +/// (they may be recognized as "not set" by some frontend libraries like qt) +constexpr char EMPTY_PLACEHOLDER[] = "[empty]"; + ParamPackage::ParamPackage(const std::string& serialized) { + if (serialized == EMPTY_PLACEHOLDER) { + return; + } + std::vector<std::string> pairs; Common::SplitString(serialized, PARAM_SEPARATOR, pairs); @@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d std::string ParamPackage::Serialize() const { if (data.empty()) - return ""; + return EMPTY_PLACEHOLDER; std::string result; @@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const { return data.find(key) != data.end(); } +void ParamPackage::Erase(const std::string& key) { + data.erase(key); +} + +void ParamPackage::Clear() { + data.clear(); +} + } // namespace Common diff --git a/src/common/param_package.h b/src/common/param_package.h index 7842cd4ef..6a0a9b656 100644 --- a/src/common/param_package.h +++ b/src/common/param_package.h @@ -32,6 +32,8 @@ public: void Set(const std::string& key, int value); void Set(const std::string& key, float value); bool Has(const std::string& key) const; + void Erase(const std::string& key); + void Clear(); private: DataType data; diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index 2a913ce82..47b7526c7 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -26,6 +26,7 @@ #include "common/alignment.h" #include "common/assert.h" #include "core/file_sys/fsmitm_romfsbuild.h" +#include "core/file_sys/ips_layer.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_vector.h" @@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) { return count; } -void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, +void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext, std::shared_ptr<RomFSBuildDirectoryContext> parent) { std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs; @@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); child->path = parent->path + "/" + kv.first; + if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr) + continue; + // Sanity check on path_len ASSERT(child->path_len < FS_MAX_PATH); @@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); child->path = parent->path + "/" + kv.first; + if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr) + continue; + // Sanity check on path_len ASSERT(child->path_len < FS_MAX_PATH); child->source = root_romfs->GetFileRelative(child->path); + if (ext != nullptr) { + const auto ips = ext->GetFileRelative(child->path + ".ips"); + + if (ips != nullptr) { + auto patched = PatchIPS(child->source, ips); + if (patched != nullptr) + child->source = std::move(patched); + } + } + child->size = child->source->GetSize(); AddFile(parent, child); @@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, } for (auto& child : child_dirs) { - this->VisitDirectory(root_romfs, child); + this->VisitDirectory(root_romfs, ext, child); } } @@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare return true; } -RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) { +RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_) + : base(std::move(base_)), ext(std::move(ext_)) { root = std::make_shared<RomFSBuildDirectoryContext>(); root->path = "\0"; directories.emplace(root->path, root); num_dirs = 1; dir_table_size = 0x18; - VisitDirectory(base, root); + VisitDirectory(base, ext, root); } RomFSBuildContext::~RomFSBuildContext() = default; diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h index b0c3c123b..3d377b0af 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.h +++ b/src/core/file_sys/fsmitm_romfsbuild.h @@ -40,7 +40,7 @@ struct RomFSFileEntry; class RomFSBuildContext { public: - explicit RomFSBuildContext(VirtualDir base); + explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr); ~RomFSBuildContext(); // This finalizes the context. @@ -48,6 +48,7 @@ public: private: VirtualDir base; + VirtualDir ext; std::shared_ptr<RomFSBuildDirectoryContext> root; std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories; std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files; @@ -59,7 +60,8 @@ private: u64 file_hash_table_size = 0; u64 file_partition_size = 0; - void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent); + void VisitDirectory(VirtualDir filesys, VirtualDir ext, + std::shared_ptr<RomFSBuildDirectoryContext> parent); bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx); diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index df933ee36..0cadbc375 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <sstream> #include "common/assert.h" +#include "common/hex_util.h" #include "common/swap.h" #include "core/file_sys/ips_layer.h" #include "core/file_sys/vfs_vector.h" @@ -15,6 +17,12 @@ enum class IPSFileType { Error, }; +constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{ + std::pair{"\\a", "\a"}, {"\\b", "\b"}, {"\\f", "\f"}, {"\\n", "\n"}, + {"\\r", "\r"}, {"\\t", "\t"}, {"\\v", "\v"}, {"\\\\", "\\"}, + {"\\\'", "\'"}, {"\\\"", "\""}, {"\\\?", "\?"}, +}; + static IPSFileType IdentifyMagic(const std::vector<u8>& magic) { if (magic.size() != 5) return IPSFileType::Error; @@ -85,4 +93,205 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); } +IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { + Parse(); +} + +IPSwitchCompiler::~IPSwitchCompiler() = default; + +std::array<u8, 32> IPSwitchCompiler::GetBuildID() const { + return nso_build_id; +} + +bool IPSwitchCompiler::IsValid() const { + return valid; +} + +static bool StartsWith(std::string_view base, std::string_view check) { + return base.size() >= check.size() && base.substr(0, check.size()) == check; +} + +static std::string EscapeStringSequences(std::string in) { + for (const auto& seq : ESCAPE_CHARACTER_MAP) { + for (auto index = in.find(seq.first); index != std::string::npos; + index = in.find(seq.first, index)) { + in.replace(index, std::strlen(seq.first), seq.second); + index += std::strlen(seq.second); + } + } + + return in; +} + +void IPSwitchCompiler::ParseFlag(const std::string& line) { + if (StartsWith(line, "@flag offset_shift ")) { + // Offset Shift Flag + offset_shift = std::stoll(line.substr(19), nullptr, 0); + } else if (StartsWith(line, "@little-endian")) { + // Set values to read as little endian + is_little_endian = true; + } else if (StartsWith(line, "@big-endian")) { + // Set values to read as big endian + is_little_endian = false; + } else if (StartsWith(line, "@flag print_values")) { + // Force printing of applied values + print_values = true; + } +} + +void IPSwitchCompiler::Parse() { + const auto bytes = patch_text->ReadAllBytes(); + std::stringstream s; + s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size()); + + std::vector<std::string> lines; + std::string stream_line; + while (std::getline(s, stream_line)) { + // Remove a trailing \r + if (!stream_line.empty() && stream_line.back() == '\r') + stream_line.pop_back(); + lines.push_back(std::move(stream_line)); + } + + for (std::size_t i = 0; i < lines.size(); ++i) { + auto line = lines[i]; + + // Remove midline comments + std::size_t comment_index = std::string::npos; + bool within_string = false; + for (std::size_t k = 0; k < line.size(); ++k) { + if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) { + within_string = !within_string; + } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) { + comment_index = k; + break; + } + } + + if (!StartsWith(line, "//") && comment_index != std::string::npos) { + last_comment = line.substr(comment_index + 2); + line = line.substr(0, comment_index); + } + + if (StartsWith(line, "@stop")) { + // Force stop + break; + } else if (StartsWith(line, "@nsobid-")) { + // NSO Build ID Specifier + auto raw_build_id = line.substr(8); + if (raw_build_id.size() != 0x40) + raw_build_id.resize(0x40, '0'); + nso_build_id = Common::HexStringToArray<0x20>(raw_build_id); + } else if (StartsWith(line, "#")) { + // Mandatory Comment + LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}", + patch_text->GetName(), line.substr(1)); + } else if (StartsWith(line, "//")) { + // Normal Comment + last_comment = line.substr(2); + if (last_comment.find_first_not_of(' ') == std::string::npos) + continue; + if (last_comment.find_first_not_of(' ') != 0) + last_comment = last_comment.substr(last_comment.find_first_not_of(' ')); + } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) { + // Start of patch + const auto enabled = StartsWith(line, "@enabled"); + if (i == 0) + return; + LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})", + patch_text->GetName(), last_comment, line.substr(1)); + + IPSwitchPatch patch{last_comment, enabled, {}}; + + // Read rest of patch + while (true) { + if (i + 1 >= lines.size()) + break; + const auto patch_line = lines[++i]; + + // Start of new patch + if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) { + --i; + break; + } + + // Check for a flag + if (StartsWith(patch_line, "@")) { + ParseFlag(patch_line); + continue; + } + + // 11 - 8 hex digit offset + space + minimum two digit overwrite val + if (patch_line.length() < 11) + break; + auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); + offset += offset_shift; + + std::vector<u8> replace; + // 9 - first char of replacement val + if (patch_line[9] == '\"') { + // string replacement + auto end_index = patch_line.find('\"', 10); + if (end_index == std::string::npos || end_index < 10) + return; + while (patch_line[end_index - 1] == '\\') { + end_index = patch_line.find('\"', end_index + 1); + if (end_index == std::string::npos || end_index < 10) + return; + } + + auto value = patch_line.substr(10, end_index - 10); + value = EscapeStringSequences(value); + replace.reserve(value.size()); + std::copy(value.begin(), value.end(), std::back_inserter(replace)); + } else { + // hex replacement + const auto value = patch_line.substr(9); + replace.reserve(value.size() / 2); + replace = Common::HexStringToVector(value, is_little_endian); + } + + if (print_values) { + LOG_INFO(Loader, + "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " + "with byte string '{}'", + patch_text->GetName(), offset, Common::HexVectorToString(replace)); + } + + patch.records.insert_or_assign(offset, std::move(replace)); + } + + patches.push_back(std::move(patch)); + } else if (StartsWith(line, "@")) { + ParseFlag(line); + } + } + + valid = true; +} + +VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { + if (in == nullptr || !valid) + return nullptr; + + auto in_data = in->ReadAllBytes(); + + for (const auto& patch : patches) { + if (!patch.enabled) + continue; + + for (const auto& record : patch.records) { + if (record.first >= in_data.size()) + continue; + auto replace_size = record.second.size(); + if (record.first + replace_size > in_data.size()) + replace_size = in_data.size() - record.first; + for (std::size_t i = 0; i < replace_size; ++i) + in_data[i + record.first] = record.second[i]; + } + } + + return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); +} + } // namespace FileSys diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h index 81c163494..57da00da8 100644 --- a/src/core/file_sys/ips_layer.h +++ b/src/core/file_sys/ips_layer.h @@ -12,4 +12,34 @@ namespace FileSys { VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips); +class IPSwitchCompiler { +public: + explicit IPSwitchCompiler(VirtualFile patch_text); + ~IPSwitchCompiler(); + + std::array<u8, 0x20> GetBuildID() const; + bool IsValid() const; + VirtualFile Apply(const VirtualFile& in) const; + +private: + void ParseFlag(const std::string& flag); + void Parse(); + + bool valid = false; + + struct IPSwitchPatch { + std::string name; + bool enabled; + std::map<u32, std::vector<u8>> records; + }; + + VirtualFile patch_text; + std::vector<IPSwitchPatch> patches; + std::array<u8, 0x20> nso_build_id{}; + bool is_little_endian = false; + s64 offset_shift = 0; + bool print_values = false; + std::string last_comment = ""; +}; + } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 539698f6e..b14d7cb0a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -73,27 +73,38 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { return exefs; } -static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs, - const std::string& build_id) { - std::vector<VirtualFile> ips; - ips.reserve(patch_dirs.size()); +static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) { + std::vector<VirtualFile> out; + out.reserve(patch_dirs.size()); for (const auto& subdir : patch_dirs) { auto exefs_dir = subdir->GetSubdirectory("exefs"); if (exefs_dir != nullptr) { for (const auto& file : exefs_dir->GetFiles()) { - if (file->GetExtension() != "ips") - continue; - auto name = file->GetName(); - const auto p1 = name.substr(0, name.find('.')); - const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1); - - if (build_id == this_build_id) - ips.push_back(file); + if (file->GetExtension() == "ips") { + auto name = file->GetName(); + const auto p1 = name.substr(0, name.find('.')); + const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1); + + if (build_id == this_build_id) + out.push_back(file); + } else if (file->GetExtension() == "pchtxt") { + IPSwitchCompiler compiler{file}; + if (!compiler.IsValid()) + continue; + + auto this_build_id = Common::HexArrayToString(compiler.GetBuildID()); + this_build_id = + this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1); + + if (build_id == this_build_id) + out.push_back(file); + } } } } - return ips; + return out; } std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { @@ -115,15 +126,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - const auto ips = CollectIPSPatches(patch_dirs, build_id); + const auto patches = CollectPatches(patch_dirs, build_id); auto out = nso; - for (const auto& ips_file : ips) { - LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"", - ips_file->GetContainingDirectory()->GetParentDirectory()->GetName()); - const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file); - if (patched != nullptr) - out = patched->ReadAllBytes(); + for (const auto& patch_file : patches) { + if (patch_file->GetExtension() == "ips") { + LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"", + patch_file->GetContainingDirectory()->GetParentDirectory()->GetName()); + const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file); + if (patched != nullptr) + out = patched->ReadAllBytes(); + } else if (patch_file->GetExtension() == "pchtxt") { + LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"", + patch_file->GetContainingDirectory()->GetParentDirectory()->GetName()); + const IPSwitchCompiler compiler{patch_file}; + const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out)); + if (patched != nullptr) + out = patched->ReadAllBytes(); + } } if (out.size() < 0x100) @@ -143,7 +163,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - return !CollectIPSPatches(patch_dirs, build_id).empty(); + return !CollectPatches(patch_dirs, build_id).empty(); } static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { @@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); std::vector<VirtualDir> layers; + std::vector<VirtualDir> layers_ext; layers.reserve(patch_dirs.size() + 1); + layers_ext.reserve(patch_dirs.size() + 1); for (const auto& subdir : patch_dirs) { auto romfs_dir = subdir->GetSubdirectory("romfs"); if (romfs_dir != nullptr) layers.push_back(std::move(romfs_dir)); + + auto ext_dir = subdir->GetSubdirectory("romfs_ext"); + if (ext_dir != nullptr) + layers_ext.push_back(std::move(ext_dir)); } layers.push_back(std::move(extracted)); @@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } - auto packed = CreateRomFS(std::move(layered)); + auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext)); + + auto packed = CreateRomFS(std::move(layered), std::move(layered_ext)); if (packed == nullptr) { return; } @@ -184,8 +212,8 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t romfs = std::move(packed); } -VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, - ContentRecordType type) const { +VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, + VirtualFile update_raw) const { LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, static_cast<u8>(type)); @@ -205,6 +233,13 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0))); romfs = new_nca->GetRomFS(); } + } else if (update_raw != nullptr) { + const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); + if (new_nca->GetStatus() == Loader::ResultStatus::Success && + new_nca->GetRomFS() != nullptr) { + LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); + romfs = new_nca->GetRomFS(); + } } // LayeredFS @@ -224,7 +259,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) { return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); } -std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const { +std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames( + VirtualFile update_raw) const { std::map<std::string, std::string, std::less<>> out; const auto installed = Service::FileSystem::GetUnionContents(); @@ -245,6 +281,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam "Update", FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements)); } + } else if (update_raw != nullptr) { + out.insert_or_assign("Update", "PACKED"); } } @@ -253,8 +291,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (mod_dir != nullptr && mod_dir->GetSize() > 0) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; - if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs"))) - AppendCommaIfNotEmpty(types, "IPS"); + + const auto exefs_dir = mod->GetSubdirectory("exefs"); + if (IsDirValidAndNonEmpty(exefs_dir)) { + bool ips = false; + bool ipswitch = false; + + for (const auto& file : exefs_dir->GetFiles()) { + if (file->GetExtension() == "ips") + ips = true; + else if (file->GetExtension() == "pchtxt") + ipswitch = true; + } + + if (ips) + AppendCommaIfNotEmpty(types, "IPS"); + if (ipswitch) + AppendCommaIfNotEmpty(types, "IPSwitch"); + } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 6a864ec43..eb6fc4607 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -36,6 +36,7 @@ public: // Currently tracked NSO patches: // - IPS + // - IPSwitch std::vector<u8> PatchNSO(const std::vector<u8>& nso) const; // Checks to see if PatchNSO() will have any effect given the NSO's build ID. @@ -46,11 +47,13 @@ public: // - Game Updates // - LayeredFS VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, - ContentRecordType type = ContentRecordType::Program) const; + ContentRecordType type = ContentRecordType::Program, + VirtualFile update_raw = nullptr) const; // Returns a vector of pairs between patch names and patch versions. // i.e. Update 3.2.2 will return {"Update", "3.2.2"} - std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const; + std::map<std::string, std::string, std::less<>> GetPatchVersionNames( + VirtualFile update_raw = nullptr) const; // Given title_id of the program, attempts to get the control data of the update and parse it, // falling back to the base control data. diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index 5910f7046..81e1f66ac 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { return out; } -VirtualFile CreateRomFS(VirtualDir dir) { +VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) { if (dir == nullptr) return nullptr; - RomFSBuildContext ctx{dir}; + RomFSBuildContext ctx{dir, ext}; return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName()); } diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index ecd1eb725..0ec404731 100644 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file, // Converts a VFS filesystem into a RomFS binary // Returns nullptr on failure -VirtualFile CreateRomFS(VirtualDir dir); +VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr); } // namespace FileSys diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index 4994c2532..0b645b106 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { RomFSFactory::~RomFSFactory() = default; +void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { + this->update_raw = std::move(update_raw); +} + ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { if (!updatable) return MakeResult<VirtualFile>(file); const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID()); - return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset)); + return MakeResult<VirtualFile>( + patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); } ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 2cace8180..7724c0b23 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -32,11 +32,13 @@ public: explicit RomFSFactory(Loader::AppLoader& app_loader); ~RomFSFactory(); + void SetPackedUpdate(VirtualFile update_raw); ResultVal<VirtualFile> OpenCurrentProcess(); ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); private: VirtualFile file; + VirtualFile update_raw; bool updatable; u64 ivfc_offset; }; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index 09bf077cd..ab5dc900c 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -259,8 +259,11 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { auto next_nca = std::make_shared<NCA>(next_file); if (next_nca->GetType() == NCAContentType::Program) program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); - if (next_nca->GetStatus() == Loader::ResultStatus::Success) + if (next_nca->GetStatus() == Loader::ResultStatus::Success || + (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && + (cnmt.GetTitleID() & 0x800) != 0)) { ncas_title[rec.type] = std::move(next_nca); + } } break; diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index 873d6c516..d4c91d529 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -17,6 +17,10 @@ namespace Kernel { ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {} ClientPort::~ClientPort() = default; +SharedPtr<ServerPort> ClientPort::GetServerPort() const { + return server_port; +} + ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { // Note: Threads do not wait for the server endpoint to call // AcceptSession before returning from this call. diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h index f3dfebbb1..6cd607206 100644 --- a/src/core/hle/kernel/client_port.h +++ b/src/core/hle/kernel/client_port.h @@ -30,6 +30,8 @@ public: return HANDLE_TYPE; } + SharedPtr<ServerPort> GetServerPort() const; + /** * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's * list of pending sessions, and signals the ServerPort, causing any threads diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index 62fb51349..e52f8245f 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/wait_object.h" +#include "core/hle/result.h" namespace Kernel { diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 79580bcd9..0ecfb5af1 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -61,13 +61,13 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs AOC_U::~AOC_U() = default; void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); - rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [¤t](u64 tid) { - return (tid & DLC_BASE_TITLE_ID_MASK) == current; - })); + rb.Push<u32>(static_cast<u32>( + std::count_if(add_on_content.begin(), add_on_content.end(), + [¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; }))); } void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { @@ -91,7 +91,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { return; } - count = std::min<size_t>(out.size() - offset, count); + count = static_cast<u32>(std::min<size_t>(out.size() - offset, count)); std::rotate(out.begin(), out.begin() + offset, out.end()); out.resize(count); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index aed2abb71..439e62d27 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -264,6 +264,15 @@ ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { return RESULT_SUCCESS; } +void SetPackedUpdate(FileSys::VirtualFile update_raw) { + LOG_TRACE(Service_FS, "Setting packed update for romfs"); + + if (romfs_factory == nullptr) + return; + + romfs_factory->SetPackedUpdate(std::move(update_raw)); +} + ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { LOG_TRACE(Service_FS, "Opening RomFS for current process"); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 7039a2247..53b01bb01 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -39,6 +39,7 @@ ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); +void SetPackedUpdate(FileSys::VirtualFile update_raw); ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess(); ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index d8b8037a8..7555bbe7d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -157,7 +157,12 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset); const auto itr = buffer_mappings.find(params.offset); - ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping"); + if (itr == buffer_mappings.end()) { + LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset); + // Hardware tests shows that unmapping an already unmapped buffer always returns successful + // and doesn't fail. + return 0; + } auto& system_instance = Core::System::GetInstance(); diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index da2c51082..4f8145dda 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -6,9 +6,12 @@ #include <memory> #include <string> +#include <type_traits> #include <unordered_map> +#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/server_port.h" #include "core/hle/result.h" #include "core/hle/service/service.h" @@ -48,6 +51,22 @@ public: ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name); + template <typename T> + std::shared_ptr<T> GetService(const std::string& service_name) const { + static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>, + "Not a base of ServiceFrameworkBase"); + auto service = registered_services.find(service_name); + if (service == registered_services.end()) { + LOG_DEBUG(Service, "Can't find service: {}", service_name); + return nullptr; + } + auto port = service->second->GetServerPort(); + if (port == nullptr) { + return nullptr; + } + return std::static_pointer_cast<T>(port->hle_handler); + } + void InvokeControlRequest(Kernel::HLERequestContext& context); private: diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 9a86e5824..951fd8257 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <cinttypes> +#include <cstring> #include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" @@ -140,7 +141,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process) const FileSys::VirtualFile module_file = dir->GetFile(module); if (module_file != nullptr) { const VAddr load_addr = next_load_addr; - next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, pm); + next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, + std::strcmp(module, "rtld") == 0, pm); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); // Register module with GDBStub GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index f2a183ba1..91659ec17 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 58> RESULT_MESSAGES{ +constexpr std::array<const char*, 59> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -152,6 +152,7 @@ constexpr std::array<const char*, 58> RESULT_MESSAGES{ "The BKTR-type NCA has a bad Relocation bucket.", "The BKTR-type NCA has a bad Subsection bucket.", "The BKTR-type NCA is missing the base RomFS.", + "The NSP or XCI does not contain an update in addition to the base game.", }; std::ostream& operator<<(std::ostream& os, ResultStatus status) { diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 20e66109b..0e0333db5 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -114,6 +114,7 @@ enum class ResultStatus : u16 { ErrorBadRelocationBuckets, ErrorBadSubsectionBuckets, ErrorMissingBKTRBaseRomFS, + ErrorNoPackedUpdate, }; std::ostream& operator<<(std::ostream& os, ResultStatus status); @@ -196,10 +197,19 @@ public: /** * Get the RomFS of the application * Since the RomFS can be huge, we return a file reference instead of copying to a buffer - * @param dir The directory containing the RomFS + * @param file The directory containing the RomFS * @return ResultStatus result of function */ - virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) { + virtual ResultStatus ReadRomFS(FileSys::VirtualFile& file) { + return ResultStatus::ErrorNotImplemented; + } + + /** + * Get the raw update of the application, should it come packed with one + * @param file The raw update NCA file (Program-type + * @return ResultStatus result of function + */ + virtual ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) { return ResultStatus::ErrorNotImplemented; } diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index 073fb9d2f..42f4a777b 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -72,6 +72,10 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { return nca_loader->ReadRomFS(dir); } +u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { + return nca_loader->ReadRomFSIVFCOffset(); +} + ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index fc3c01876..b4d93bd01 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -36,6 +36,7 @@ public: ResultStatus Load(Kernel::Process& process) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; private: diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index c10f826a4..25dd3f04e 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -18,7 +18,9 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/nro.h" +#include "core/loader/nso.h" #include "core/memory.h" +#include "core/settings.h" namespace Loader { @@ -150,6 +152,19 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size); } + if (!Settings::values.program_args.empty()) { + const auto arg_data = Settings::values.program_args; + codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; + NSOArgumentHeader args_header{ + NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; + const auto end_offset = program_image.size(); + program_image.resize(static_cast<u32>(program_image.size()) + + NSO_ARGUMENT_DATA_ALLOCATION_SIZE); + std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader)); + std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), + arg_data.size()); + } + // Read MOD header ModHeader mod_header{}; // Default .bss to NRO header bss size if MOD0 section doesn't exist diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 2186b02af..28c6dd9b7 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -17,6 +17,7 @@ #include "core/hle/kernel/vm_manager.h" #include "core/loader/nso.h" #include "core/memory.h" +#include "core/settings.h" namespace Loader { @@ -94,6 +95,7 @@ static constexpr u32 PageAlignSize(u32 size) { } VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, + bool should_pass_arguments, boost::optional<FileSys::PatchManager> pm) { if (file == nullptr) return {}; @@ -125,6 +127,19 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); } + if (should_pass_arguments && !Settings::values.program_args.empty()) { + const auto arg_data = Settings::values.program_args; + codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; + NSOArgumentHeader args_header{ + NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; + const auto end_offset = program_image.size(); + program_image.resize(static_cast<u32>(program_image.size()) + + NSO_ARGUMENT_DATA_ALLOCATION_SIZE); + std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader)); + std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), + arg_data.size()); + } + // MOD header pointer is at .text offset + 4 u32 module_offset; std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); @@ -172,7 +187,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) { // Load module const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); - LoadModule(file, base_address); + LoadModule(file, base_address, true); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 05353d4d9..70ab3b718 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -11,6 +11,15 @@ namespace Loader { +constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; + +struct NSOArgumentHeader { + u32_le allocated_size; + u32_le actual_size; + INSERT_PADDING_BYTES(0x18); +}; +static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size."); + /// Loads an NSO file class AppLoader_NSO final : public AppLoader, Linker { public: @@ -27,7 +36,7 @@ public: return IdentifyType(file); } - static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, + static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments, boost::optional<FileSys::PatchManager> pm = boost::none); ResultStatus Load(Kernel::Process& process) override; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index b7ba77ef4..5534ce01c 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -10,8 +10,10 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/submission_package.h" #include "core/hle/kernel/process.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/nca.h" #include "core/loader/nsp.h" @@ -91,13 +93,39 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) { if (result != ResultStatus::Success) return result; + FileSys::VirtualFile update_raw; + if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) + Service::FileSystem::SetPackedUpdate(std::move(update_raw)); + is_loaded = true; return ResultStatus::Success; } -ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) { - return secondary_loader->ReadRomFS(dir); +ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) { + return secondary_loader->ReadRomFS(file); +} + +u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { + return secondary_loader->ReadRomFSIVFCOffset(); +} + +ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& file) { + if (nsp->IsExtractedType()) + return ResultStatus::ErrorNoPackedUpdate; + + const auto read = + nsp->GetNCAFile(FileSys::GetUpdateTitleID(title_id), FileSys::ContentRecordType::Program); + + if (read == nullptr) + return ResultStatus::ErrorNoPackedUpdate; + const auto nca_test = std::make_shared<FileSys::NCA>(read); + + if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS) + return nca_test->GetStatus(); + + file = read; + return ResultStatus::Success; } ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) { diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index eac9b819a..b006594a6 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -37,7 +37,9 @@ public: ResultStatus Load(Kernel::Process& process) override; - ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& file) override; + u64 ReadRomFSIVFCOffset() const override; + ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index eda67a8c8..ee5452eb9 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -9,7 +9,11 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/submission_package.h" #include "core/hle/kernel/process.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/loader/nca.h" #include "core/loader/xci.h" @@ -63,13 +67,41 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) { if (result != ResultStatus::Success) return result; + FileSys::VirtualFile update_raw; + if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) + Service::FileSystem::SetPackedUpdate(std::move(update_raw)); + is_loaded = true; return ResultStatus::Success; } -ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { - return nca_loader->ReadRomFS(dir); +ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) { + return nca_loader->ReadRomFS(file); +} + +u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { + return nca_loader->ReadRomFSIVFCOffset(); +} + +ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& file) { + u64 program_id{}; + nca_loader->ReadProgramId(program_id); + if (program_id == 0) + return ResultStatus::ErrorXCIMissingProgramNCA; + + const auto read = xci->GetSecurePartitionNSP()->GetNCAFile( + FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program); + + if (read == nullptr) + return ResultStatus::ErrorNoPackedUpdate; + const auto nca_test = std::make_shared<FileSys::NCA>(read); + + if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS) + return nca_test->GetStatus(); + + file = read; + return ResultStatus::Success; } ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 17e47b658..770ed1437 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -37,7 +37,9 @@ public: ResultStatus Load(Kernel::Process& process) override; - ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& file) override; + u64 ReadRomFSIVFCOffset() const override; + ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; diff --git a/src/core/settings.h b/src/core/settings.h index 1808f5937..83b9a04c8 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -155,6 +155,7 @@ struct Values { // Debugging bool use_gdbstub; u16 gdbstub_port; + std::string program_args; // WebService bool enable_telemetry; diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index ea1555c5d..912e785b9 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -4,11 +4,13 @@ #include "core/memory.h" #include "video_core/engines/fermi_2d.h" +#include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" namespace Tegra::Engines { -Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) + : memory_manager(memory_manager), rasterizer{rasterizer} {} void Fermi2D::WriteReg(u32 method, u32 value) { ASSERT_MSG(method < Regs::NUM_REGS, @@ -44,27 +46,31 @@ void Fermi2D::HandleSurfaceCopy() { u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format); u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format); - if (regs.src.linear == regs.dst.linear) { - // If the input layout and the output layout are the same, just perform a raw copy. - ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight()); - Memory::CopyBlock(dest_cpu, source_cpu, - src_bytes_per_pixel * regs.dst.width * regs.dst.height); - return; - } - - u8* src_buffer = Memory::GetPointer(source_cpu); - u8* dst_buffer = Memory::GetPointer(dest_cpu); - - if (!regs.src.linear && regs.dst.linear) { - // If the input is tiled and the output is linear, deswizzle the input and copy it over. - Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, - dst_bytes_per_pixel, src_buffer, dst_buffer, true, - regs.src.BlockHeight()); - } else { - // If the input is linear and the output is tiled, swizzle the input and copy it over. - Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, - dst_bytes_per_pixel, dst_buffer, src_buffer, false, - regs.dst.BlockHeight()); + if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) { + // TODO(bunnei): The below implementation currently will not get hit, as + // AccelerateSurfaceCopy tries to always copy and will always return success. This should be + // changed once we properly support flushing. + + if (regs.src.linear == regs.dst.linear) { + // If the input layout and the output layout are the same, just perform a raw copy. + ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight()); + Memory::CopyBlock(dest_cpu, source_cpu, + src_bytes_per_pixel * regs.dst.width * regs.dst.height); + return; + } + u8* src_buffer = Memory::GetPointer(source_cpu); + u8* dst_buffer = Memory::GetPointer(dest_cpu); + if (!regs.src.linear && regs.dst.linear) { + // If the input is tiled and the output is linear, deswizzle the input and copy it over. + Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, + dst_bytes_per_pixel, src_buffer, dst_buffer, true, + regs.src.BlockHeight()); + } else { + // If the input is linear and the output is tiled, swizzle the input and copy it over. + Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, + dst_bytes_per_pixel, dst_buffer, src_buffer, false, + regs.dst.BlockHeight()); + } } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 021b83eaa..81d15c62a 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -12,6 +12,10 @@ #include "video_core/gpu.h" #include "video_core/memory_manager.h" +namespace VideoCore { +class RasterizerInterface; +} + namespace Tegra::Engines { #define FERMI2D_REG_INDEX(field_name) \ @@ -19,7 +23,7 @@ namespace Tegra::Engines { class Fermi2D final { public: - explicit Fermi2D(MemoryManager& memory_manager); + explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager); ~Fermi2D() = default; /// Write the value to the register identified by method. @@ -94,6 +98,8 @@ public: MemoryManager& memory_manager; private: + VideoCore::RasterizerInterface& rasterizer; + /// Performs the copy from the source surface to the destination surface as configured in the /// registers. void HandleSurfaceCopy(); diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index baa8b63b7..9ba7e3533 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -25,7 +25,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) { GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { memory_manager = std::make_unique<Tegra::MemoryManager>(); maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager); - fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager); + fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager); kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager); diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index cd819d69f..06fc59dbe 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -5,6 +5,7 @@ #pragma once #include "common/common_types.h" +#include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" @@ -33,13 +34,9 @@ public: /// and invalidated virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; - /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0 - virtual bool AccelerateDisplayTransfer(const void* config) { - return false; - } - - /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1 - virtual bool AccelerateTextureCopy(const void* config) { + /// Attempt to use a faster method to perform a surface copy + virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, + const Tegra::Engines::Fermi2D::Regs::Surface& dst) { return false; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 60dcdc184..209bdf181 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -252,6 +252,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() { params.count = regs.vertex_buffer.count; params.vertex_first = regs.vertex_buffer.first; } + return params; } void RasterizerOpenGL::SetupShaders() { @@ -616,14 +617,10 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { InvalidateRegion(addr, size); } -bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) { +bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, + const Tegra::Engines::Fermi2D::Regs::Surface& dst) { MICROPROFILE_SCOPE(OpenGL_Blits); - UNREACHABLE(); - return true; -} - -bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) { - UNREACHABLE(); + res_cache.FermiCopySurface(src, dst); return true; } @@ -661,10 +658,13 @@ void RasterizerOpenGL::SamplerInfo::Create() { sampler.Create(); mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear; wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap; + uses_depth_compare = false; + depth_compare_func = Tegra::Texture::DepthCompareFunc::Never; // default is GL_LINEAR_MIPMAP_LINEAR glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Other attributes have correct defaults + glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER); } void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { @@ -692,6 +692,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p)); } + if (uses_depth_compare != (config.depth_compare_enabled == 1)) { + uses_depth_compare = (config.depth_compare_enabled == 1); + if (uses_depth_compare) { + glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + } else { + glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + } + + if (depth_compare_func != config.depth_compare_func) { + depth_compare_func = config.depth_compare_func; + glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, + MaxwellToGL::DepthCompareFunc(depth_compare_func)); + } + if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border || wrap_p == Tegra::Texture::WrapMode::Border) { const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g, diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index bf954bb5d..0dab2018b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -52,8 +52,8 @@ public: void FlushRegion(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size) override; void FlushAndInvalidateRegion(VAddr addr, u64 size) override; - bool AccelerateDisplayTransfer(const void* config) override; - bool AccelerateTextureCopy(const void* config) override; + bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, + const Tegra::Engines::Fermi2D::Regs::Surface& dst) override; bool AccelerateFill(const void* config) override; bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) override; @@ -96,6 +96,8 @@ private: Tegra::Texture::WrapMode wrap_u; Tegra::Texture::WrapMode wrap_v; Tegra::Texture::WrapMode wrap_p; + bool uses_depth_compare; + Tegra::Texture::DepthCompareFunc depth_compare_func; GLvec4 border_color; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index ce967c4d6..56ff83eff 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -143,6 +143,28 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { return params; } +/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params{}; + params.addr = TryGetCpuAddr(config.Address()); + params.is_tiled = !config.linear; + params.block_height = params.is_tiled ? config.BlockHeight() : 0, + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + params.width = config.width; + params.height = config.height; + params.unaligned_height = config.height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.size_in_bytes_total = params.SizeInBytesTotal(); + params.size_in_bytes_2d = params.SizeInBytes2D(); + params.max_mip_level = 0; + params.rt = {}; + + return params; +} + static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S @@ -559,6 +581,18 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface, return true; } +static void FastCopySurface(const Surface& src_surface, const Surface& dst_surface) { + const auto& src_params{src_surface->GetSurfaceParams()}; + const auto& dst_params{dst_surface->GetSurfaceParams()}; + + const u32 width{std::min(src_params.width, dst_params.width)}; + const u32 height{std::min(src_params.height, dst_params.height)}; + + glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0, + 0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0, + 0, 0, width, height, 1); +} + static void CopySurface(const Surface& src_surface, const Surface& dst_surface, GLuint copy_pbo_handle, GLenum src_attachment = 0, GLenum dst_attachment = 0, std::size_t cubemap_face = 0) { @@ -1033,6 +1067,26 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) { return surface; } +void RasterizerCacheOpenGL::FermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& src_config, + const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) { + + const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config); + const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config); + + ASSERT(src_params.width == dst_params.width); + ASSERT(src_params.height == dst_params.height); + ASSERT(src_params.pixel_format == dst_params.pixel_format); + ASSERT(src_params.block_height == dst_params.block_height); + ASSERT(src_params.is_tiled == dst_params.is_tiled); + ASSERT(src_params.depth == dst_params.depth); + ASSERT(src_params.depth == 1); // Currently, FastCopySurface only works with 2D surfaces + ASSERT(src_params.target == dst_params.target); + ASSERT(src_params.rt.index == dst_params.rt.index); + + FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false)); +} + Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, const SurfaceParams& new_params) { // Verify surface is compatible for blitting @@ -1041,6 +1095,15 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, // Get a new surface with the new parameters, and blit the previous surface to it Surface new_surface{GetUncachedSurface(new_params)}; + // For compatible surfaces, we can just do fast glCopyImageSubData based copy + if (old_params.target == new_params.target && old_params.type == new_params.type && + old_params.depth == new_params.depth && old_params.depth == 1 && + SurfaceParams::GetFormatBpp(old_params.pixel_format) == + SurfaceParams::GetFormatBpp(new_params.pixel_format)) { + FastCopySurface(old_surface, new_surface); + return new_surface; + } + // If the format is the same, just do a framebuffer blit. This is significantly faster than // using PBOs. The is also likely less accurate, as textures will be converted rather than // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 49025a3fe..0b4940b3c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -13,6 +13,7 @@ #include "common/common_types.h" #include "common/hash.h" #include "common/math_util.h" +#include "video_core/engines/fermi_2d.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -719,6 +720,10 @@ struct SurfaceParams { Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format); + /// Creates SurfaceParams for a Fermi2D surface copy + static SurfaceParams CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config); + /// Checks if surfaces are compatible for caching bool IsCompatibleSurface(const SurfaceParams& other) const { return std::tie(pixel_format, type, width, height, target, depth) == @@ -837,6 +842,10 @@ public: /// Tries to find a framebuffer using on the provided CPU address Surface TryFindFramebufferSurface(VAddr addr) const; + /// Copies the contents of one surface to another + void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config, + const Tegra::Engines::Fermi2D::Regs::Surface& dst_config); + private: void LoadSurface(const Surface& surface); Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 579a78702..7e57de78a 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -508,7 +508,7 @@ public: /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if /// necessary. std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, - bool is_array) { + bool is_array, bool is_shadow) { const std::size_t offset = static_cast<std::size_t>(sampler.index.Value()); // If this sampler has already been used, return the existing mapping. @@ -517,13 +517,14 @@ public: [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); if (itr != used_samplers.end()) { - ASSERT(itr->GetType() == type && itr->IsArray() == is_array); + ASSERT(itr->GetType() == type && itr->IsArray() == is_array && + itr->IsShadow() == is_shadow); return itr->GetName(); } // Otherwise create a new mapping for this sampler const std::size_t next_index = used_samplers.size(); - const SamplerEntry entry{stage, offset, next_index, type, is_array}; + const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; used_samplers.emplace_back(entry); return entry.GetName(); } @@ -747,8 +748,9 @@ private: } /// Generates code representing a texture sampler. - std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) { - return regs.AccessSampler(sampler, type, is_array); + std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array, + bool is_shadow) { + return regs.AccessSampler(sampler, type, is_array, is_shadow); } /** @@ -1002,6 +1004,24 @@ private: shader.AddLine('}'); } + static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) { + switch (texture_type) { + case Tegra::Shader::TextureType::Texture1D: { + return 1; + } + case Tegra::Shader::TextureType::Texture2D: { + return 2; + } + case Tegra::Shader::TextureType::TextureCube: { + return 3; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", static_cast<u32>(texture_type)); + UNREACHABLE(); + return 0; + } + } + /* * Emits code to push the input target address to the SSY address stack, incrementing the stack * top. @@ -1896,24 +1916,35 @@ private: "NODEP is not implemented"); ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), "AOFFI is not implemented"); - ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), - "DC is not implemented"); - switch (texture_type) { - case Tegra::Shader::TextureType::Texture1D: { + const bool depth_compare = + instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); + u32 num_coordinates = TextureCoordinates(texture_type); + if (depth_compare) + num_coordinates += 1; + + switch (num_coordinates) { + case 1: { const std::string x = regs.GetRegisterAsFloat(instr.gpr8); coord = "float coords = " + x + ';'; break; } - case Tegra::Shader::TextureType::Texture2D: { + case 2: { const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; break; } + case 3: { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + break; + } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(texture_type)); + LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", + static_cast<u32>(num_coordinates)); UNREACHABLE(); // Fallback to interpreting as a 2D texture for now @@ -1924,9 +1955,10 @@ private: } // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias // or lod. - const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); + std::string op_c; - const std::string sampler = GetSampler(instr.sampler, texture_type, false); + const std::string sampler = + GetSampler(instr.sampler, texture_type, false, depth_compare); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. @@ -1935,7 +1967,7 @@ private: shader.AddLine(coord); std::string texture; - switch (instr.tex.process_mode) { + switch (instr.tex.GetTextureProcessMode()) { case Tegra::Shader::TextureProcessMode::None: { texture = "texture(" + sampler + ", coords)"; break; @@ -1946,12 +1978,22 @@ private: } case Tegra::Shader::TextureProcessMode::LB: case Tegra::Shader::TextureProcessMode::LBA: { + if (num_coordinates <= 2) { + op_c = regs.GetRegisterAsFloat(instr.gpr20); + } else { + op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + } // TODO: Figure if A suffix changes the equation at all. texture = "texture(" + sampler + ", coords, " + op_c + ')'; break; } case Tegra::Shader::TextureProcessMode::LL: case Tegra::Shader::TextureProcessMode::LLA: { + if (num_coordinates <= 2) { + op_c = regs.GetRegisterAsFloat(instr.gpr20); + } else { + op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + } // TODO: Figure if A suffix changes the equation at all. texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; break; @@ -1959,18 +2001,22 @@ private: default: { texture = "texture(" + sampler + ", coords)"; LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.tex.process_mode.Value())); + static_cast<u32>(instr.tex.GetTextureProcessMode())); UNREACHABLE(); } } - std::size_t dest_elem{}; - for (std::size_t elem = 0; elem < 4; ++elem) { - if (!instr.tex.IsComponentEnabled(elem)) { - // Skip disabled components - continue; + if (!depth_compare) { + std::size_t dest_elem{}; + for (std::size_t elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; + } + regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); + ++dest_elem; } - regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); - ++dest_elem; + } else { + regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); } --shader.scope; shader.AddLine("}"); @@ -1983,11 +2029,15 @@ private: ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), "NODEP is not implemented"); - ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), - "DC is not implemented"); - switch (texture_type) { - case Tegra::Shader::TextureType::Texture2D: { + const bool depth_compare = + instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); + u32 num_coordinates = TextureCoordinates(texture_type); + if (depth_compare) + num_coordinates += 1; + + switch (num_coordinates) { + case 2: { if (is_array) { const std::string index = regs.GetRegisterAsInteger(instr.gpr8); const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); @@ -2000,17 +2050,25 @@ private: } break; } - case Tegra::Shader::TextureType::TextureCube: { - ASSERT_MSG(!is_array, "Unimplemented"); - std::string x = regs.GetRegisterAsFloat(instr.gpr8); - std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - std::string z = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + case 3: { + if (is_array) { + UNIMPLEMENTED_MSG("3-coordinate arrays not fully implemented"); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + texture_type = Tegra::Shader::TextureType::Texture2D; + is_array = false; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + } break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(texture_type)); + LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", + static_cast<u32>(num_coordinates)); UNREACHABLE(); // Fallback to interpreting as a 2D texture for now @@ -2020,9 +2078,35 @@ private: texture_type = Tegra::Shader::TextureType::Texture2D; is_array = false; } - const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); - const std::string texture = "texture(" + sampler + ", coords)"; - WriteTexsInstruction(instr, coord, texture); + const std::string sampler = + GetSampler(instr.sampler, texture_type, is_array, depth_compare); + std::string texture; + switch (instr.texs.GetTextureProcessMode()) { + case Tegra::Shader::TextureProcessMode::None: { + texture = "texture(" + sampler + ", coords)"; + break; + } + case Tegra::Shader::TextureProcessMode::LZ: { + texture = "textureLod(" + sampler + ", coords, 0.0)"; + break; + } + case Tegra::Shader::TextureProcessMode::LL: { + const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; + break; + } + default: { + texture = "texture(" + sampler + ", coords)"; + LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", + static_cast<u32>(instr.texs.GetTextureProcessMode())); + UNREACHABLE(); + } + } + if (!depth_compare) { + WriteTexsInstruction(instr, coord, texture); + } else { + WriteTexsInstruction(instr, coord, "vec4(" + texture + ')'); + } break; } case OpCode::Id::TLDS: { @@ -2062,9 +2146,26 @@ private: static_cast<u32>(texture_type)); UNREACHABLE(); } - - const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); - const std::string texture = "texelFetch(" + sampler + ", coords, 0)"; + const std::string sampler = + GetSampler(instr.sampler, texture_type, is_array, false); + std::string texture = "texelFetch(" + sampler + ", coords, 0)"; + const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1); + switch (instr.tlds.GetTextureProcessMode()) { + case Tegra::Shader::TextureProcessMode::LZ: { + texture = "texelFetch(" + sampler + ", coords, 0)"; + break; + } + case Tegra::Shader::TextureProcessMode::LL: { + texture = "texelFetch(" + sampler + ", coords, " + op_c + ')'; + break; + } + default: { + texture = "texelFetch(" + sampler + ", coords, 0)"; + LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", + static_cast<u32>(instr.tlds.GetTextureProcessMode())); + UNREACHABLE(); + } + } WriteTexsInstruction(instr, coord, texture); break; } @@ -2077,28 +2178,43 @@ private: "NODEP is not implemented"); ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), "AOFFI is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), - "DC is not implemented"); ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), "NDV is not implemented"); ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), "PTP is not implemented"); - - switch (instr.tld4.texture_type) { - case Tegra::Shader::TextureType::Texture2D: { + const bool depth_compare = + instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); + auto texture_type = instr.tld4.texture_type.Value(); + u32 num_coordinates = TextureCoordinates(texture_type); + if (depth_compare) + num_coordinates += 1; + + switch (num_coordinates) { + case 2: { const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; break; } + case 3: { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + break; + } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(instr.tld4.texture_type.Value())); + LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", + static_cast<u32>(num_coordinates)); UNREACHABLE(); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + texture_type = Tegra::Shader::TextureType::Texture2D; } const std::string sampler = - GetSampler(instr.sampler, instr.tld4.texture_type, false); + GetSampler(instr.sampler, texture_type, false, depth_compare); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. shader.AddLine("{"); @@ -2106,15 +2222,18 @@ private: shader.AddLine(coord); const std::string texture = "textureGather(" + sampler + ", coords, " + std::to_string(instr.tld4.component) + ')'; - - std::size_t dest_elem{}; - for (std::size_t elem = 0; elem < 4; ++elem) { - if (!instr.tex.IsComponentEnabled(elem)) { - // Skip disabled components - continue; + if (!depth_compare) { + std::size_t dest_elem{}; + for (std::size_t elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; + } + regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); + ++dest_elem; } - regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); - ++dest_elem; + } else { + regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); } --shader.scope; shader.AddLine("}"); @@ -2125,18 +2244,30 @@ private: "NODEP is not implemented"); ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), "AOFFI is not implemented"); - ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), - "DC is not implemented"); + const bool depth_compare = + instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. - const std::string sampler = - GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + const std::string sampler = GetSampler( + instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare); + std::string coord; + if (!depth_compare) { + coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + } else { + // Note: TLD4S coordinate encoding works just like TEXS's + const std::string op_c = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec3 coords = vec3(" + op_a + ", " + op_c + ", " + op_b + ");"; + } const std::string texture = "textureGather(" + sampler + ", coords, " + std::to_string(instr.tld4s.component) + ')'; - WriteTexsInstruction(instr, coord, texture); + + if (!depth_compare) { + WriteTexsInstruction(instr, coord, texture); + } else { + WriteTexsInstruction(instr, coord, "vec4(" + texture + ')'); + } break; } case OpCode::Id::TXQ: { @@ -2147,7 +2278,7 @@ private: // Sadly, not all texture instructions specify the type of texture their sampler // uses. This must be fixed at a later instance. const std::string sampler = - GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); + GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false); switch (instr.txq.query_type) { case Tegra::Shader::TextureQueryType::Dimension: { const std::string texture = "textureQueryLevels(" + sampler + ')'; @@ -2172,7 +2303,8 @@ private: const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); const bool is_array = instr.tmml.array != 0; auto texture_type = instr.tmml.texture_type.Value(); - const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); + const std::string sampler = + GetSampler(instr.sampler, texture_type, is_array, false); // TODO: add coordinates for different samplers once other texture types are // implemented. diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index d53b93ad5..e56f39e78 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -75,8 +75,9 @@ class SamplerEntry { public: SamplerEntry(Maxwell::ShaderStage stage, std::size_t offset, std::size_t index, - Tegra::Shader::TextureType type, bool is_array) - : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {} + Tegra::Shader::TextureType type, bool is_array, bool is_shadow) + : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array), + is_shadow(is_shadow) {} std::size_t GetOffset() const { return offset; @@ -117,6 +118,8 @@ public: } if (is_array) glsl_type += "Array"; + if (is_shadow) + glsl_type += "Shadow"; return glsl_type; } @@ -128,6 +131,10 @@ public: return is_array; } + bool IsShadow() const { + return is_shadow; + } + u32 GetHash() const { return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index); } @@ -147,7 +154,8 @@ private: Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. std::size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc) - bool is_array; ///< Whether the texture is being sampled as an array texture or not. + bool is_array; ///< Whether the texture is being sampled as an array texture or not. + bool is_shadow; ///< Whether the texture is being sampled as a depth texture or not. }; struct ShaderEntries { diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 67273e164..3c3bcaae4 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -159,6 +159,31 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { return {}; } +inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) { + switch (func) { + case Tegra::Texture::DepthCompareFunc::Never: + return GL_NEVER; + case Tegra::Texture::DepthCompareFunc::Less: + return GL_LESS; + case Tegra::Texture::DepthCompareFunc::LessEqual: + return GL_LEQUAL; + case Tegra::Texture::DepthCompareFunc::Equal: + return GL_EQUAL; + case Tegra::Texture::DepthCompareFunc::NotEqual: + return GL_NOTEQUAL; + case Tegra::Texture::DepthCompareFunc::Greater: + return GL_GREATER; + case Tegra::Texture::DepthCompareFunc::GreaterEqual: + return GL_GEQUAL; + case Tegra::Texture::DepthCompareFunc::Always: + return GL_ALWAYS; + } + LOG_CRITICAL(Render_OpenGL, "Unimplemented texture depth compare function ={}", + static_cast<u32>(func)); + UNREACHABLE(); + return {}; +} + inline GLenum BlendEquation(Maxwell::Blend::Equation equation) { switch (equation) { case Maxwell::Blend::Equation::Add: diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 14aea4838..8f31d825a 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -227,6 +227,17 @@ enum class WrapMode : u32 { MirrorOnceClampOGL = 7, }; +enum class DepthCompareFunc : u32 { + Never = 0, + Less = 1, + Equal = 2, + LessEqual = 3, + Greater = 4, + NotEqual = 5, + GreaterEqual = 6, + Always = 7, +}; + enum class TextureFilter : u32 { Nearest = 1, Linear = 2, @@ -244,7 +255,7 @@ struct TSCEntry { BitField<3, 3, WrapMode> wrap_v; BitField<6, 3, WrapMode> wrap_p; BitField<9, 1, u32> depth_compare_enabled; - BitField<10, 3, u32> depth_compare_func; + BitField<10, 3, DepthCompareFunc> depth_compare_func; }; union { BitField<0, 2, TextureFilter> mag_filter; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 650dd03c0..7fec15991 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -134,6 +134,7 @@ void Config::ReadValues() { qt_config->beginGroup("Debugging"); Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); + Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString(); qt_config->endGroup(); qt_config->beginGroup("WebService"); @@ -269,6 +270,7 @@ void Config::SaveValues() { qt_config->beginGroup("Debugging"); qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); + qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args)); qt_config->endGroup(); qt_config->beginGroup("WebService"); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 45d84f19a..9e765fc93 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -33,6 +33,7 @@ void ConfigureDebug::setConfiguration() { ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); + ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); } void ConfigureDebug::applyConfiguration() { @@ -40,6 +41,7 @@ void ConfigureDebug::applyConfiguration() { Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); + Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 5ae7276bd..ff4987604 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -107,6 +107,29 @@ </widget> </item> <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Homebrew</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Arguments String</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="homebrew_args_edit"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 473937ea9..94789c064 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <memory> #include <utility> +#include <QMenu> #include <QMessageBox> #include <QTimer> #include "common/param_package.h" @@ -128,28 +129,63 @@ ConfigureInput::ConfigureInput(QWidget* parent) analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (button_map[button_id]) - connect(button_map[button_id], &QPushButton::released, [=]() { - handleClick( - button_map[button_id], - [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, - InputCommon::Polling::DeviceType::Button); - }); + if (!button_map[button_id]) + continue; + button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick( + button_map[button_id], + [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, + InputCommon::Polling::DeviceType::Button); + }); + connect(button_map[button_id], &QPushButton::customContextMenuRequested, + [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + }); } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (analog_map_buttons[analog_id][sub_button_id] != nullptr) { - connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, - [=]() { - handleClick(analog_map_buttons[analog_id][sub_button_id], - [=](const Common::ParamPackage& params) { - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - }, - InputCommon::Polling::DeviceType::Button); + if (!analog_map_buttons[analog_id][sub_button_id]) + continue; + analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( + Qt::CustomContextMenu); + connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { + handleClick(analog_map_buttons[analog_id][sub_button_id], + [=](const Common::ParamPackage& params) { + SetAnalogButton(params, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + }, + InputCommon::Polling::DeviceType::Button); + }); + connect(analog_map_buttons[analog_id][sub_button_id], + &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); }); - } + context_menu.addAction(tr("Restore Default"), [&] { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogButton(params, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + }); + context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( + menu_location)); + }); } connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { QMessageBox::information(this, tr("Information"), @@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent) }); } + connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); timeout_timer->setSingleShot(true); @@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() { } } updateButtonLabels(); - applyConfiguration(); +} + +void ConfigureInput::ClearAll() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (button_map[button_id] && button_map[button_id]->isEnabled()) + buttons_param[button_id].Clear(); + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map_buttons[analog_id][sub_button_id] && + analog_map_buttons[analog_id][sub_button_id]->isEnabled()) + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + } + } + updateButtonLabels(); } void ConfigureInput::updateButtonLabels() { diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index a0bef86d5..d1198db81 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -72,6 +72,9 @@ private: void loadConfiguration(); /// Restore all buttons to their default values. void restoreDefaults(); + /// Clear all input configuration + void ClearAll(); + /// Update UI to reflect current configuration. void updateButtonLabels(); diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8bfa5df62..8a019a693 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -695,6 +695,34 @@ Capture:</string> </spacer> </item> <item> + <widget class="QPushButton" name="buttonClearAll"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Clear All</string> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="buttonRestoreDefaults"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 1947bdb93..d2b3de683 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -57,16 +57,25 @@ QString FormatGameName(const std::string& physical_name) { return physical_name_as_qstring; } -QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) { +QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, + Loader::AppLoader& loader, bool updatable = true) { QString out; - for (const auto& kv : patch_manager.GetPatchVersionNames()) { + FileSys::VirtualFile update_raw; + loader.ReadUpdateRaw(update_raw); + for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { if (!updatable && kv.first == "Update") continue; if (kv.second.empty()) { out.append(fmt::format("{}\n", kv.first).c_str()); } else { - out.append(fmt::format("{} ({})\n", kv.first, kv.second).c_str()); + auto ver = kv.second; + + // Display container name for packed updates + if (ver == "PACKED" && kv.first == "Update") + ver = Loader::GetFileTypeString(loader.GetFileType()); + + out.append(fmt::format("{} ({})\n", kv.first, ver).c_str()); } } @@ -116,7 +125,7 @@ void GameListWorker::AddInstalledTitlesToGameList() { QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), program_id), new GameListItemCompat(compatibility), - new GameListItem(FormatPatchNameVersions(patch)), + new GameListItem(FormatPatchNameVersions(patch, *loader)), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(file->GetSize()), @@ -206,7 +215,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), program_id), new GameListItemCompat(compatibility), - new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())), + new GameListItem( + FormatPatchNameVersions(patch, *loader, loader->IsRomFSUpdatable())), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ad62a82d0..e11833c5a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -485,6 +485,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() { unsupported_ext.append("ARB_texture_storage"); if (!GLAD_GL_ARB_multi_bind) unsupported_ext.append("ARB_multi_bind"); + if (!GLAD_GL_ARB_copy_image) + unsupported_ext.append("ARB_copy_image"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 9d934e220..2470f4640 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -138,6 +138,7 @@ void Config::ReadValues() { Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); + Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); // Web Service Settings::values.enable_telemetry = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 0733301b2..155095095 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -98,6 +98,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { unsupported_ext.push_back("ARB_texture_storage"); if (!GLAD_GL_ARB_multi_bind) unsupported_ext.push_back("ARB_multi_bind"); + if (!GLAD_GL_ARB_copy_image) + unsupported_ext.push_back("ARB_copy_image"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 1d951ca3f..27aba95f6 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -56,9 +56,10 @@ static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 << " [options] <filename>\n" "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" - "-f, --fullscreen Start in fullscreen mode\n" + "-f, --fullscreen Start in fullscreen mode\n" "-h, --help Display this help and exit\n" - "-v, --version Output version information and exit\n"; + "-v, --version Output version information and exit\n" + "-p, --program Pass following string as arguments to executable\n"; } static void PrintVersion() { @@ -103,15 +104,13 @@ int main(int argc, char** argv) { bool fullscreen = false; static struct option long_options[] = { - {"gdbport", required_argument, 0, 'g'}, - {"fullscreen", no_argument, 0, 'f'}, - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'}, - {0, 0, 0, 0}, + {"gdbport", required_argument, 0, 'g'}, {"fullscreen", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'}, + {"program", optional_argument, 0, 'p'}, {0, 0, 0, 0}, }; while (optind < argc) { - char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index); + char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index); if (arg != -1) { switch (arg) { case 'g': @@ -135,6 +134,10 @@ int main(int argc, char** argv) { case 'v': PrintVersion(); return 0; + case 'p': + Settings::values.program_args = argv[optind]; + ++optind; + break; } } else { #ifdef _WIN32 |