Compare commits

...

44 Commits

Author SHA1 Message Date
Lioncash
e3b4d31f4e telemetry_session: Remove doxygen comment for a non-existent parameter
There's no "func" parameter, so this can just be removed.
2018-10-09 14:52:10 -04:00
Lioncash
8aa4889e76 telemetry_session: Add missing includes
Prevents potential compilation issues in the future by including missing
headers for certain functions and types.
2018-10-09 14:51:39 -04:00
Lioncash
1964f4bbb3 telemetry_session: Remove unimplemented FinalizeAsyncJob prototype
This isn't implemented anywhere, so it can just be removed.
2018-10-09 14:46:31 -04:00
Lioncash
8723cc8798 telemetry_session: Use a std::array in GenerateTelemetryId()
We don't need to potentially heap-allocate a std::string instance here,
given the data is known ahead of time. We can just place it within an
array and pass this to the mbedtls functions.
2018-10-09 14:46:26 -04:00
bunnei
561d79e034 Merge pull request #1423 from DarkLordZach/romfs-file-exts
fsmitm_romfsbuild: Add support for stubbing and IPS patches in LFS
2018-10-08 12:31:27 -04:00
bunnei
6b48ba5271 Merge pull request #1424 from DarkLordZach/ips-witch
ips_layer: Add support for IPSwitch executable patches
2018-10-08 12:30:33 -04:00
bunnei
fd891ee9c0 Merge pull request #1456 from ogniK5377/aoc-u-fixups
Fixed assertion due to CountAddOnContent & Casting warnings
2018-10-08 01:21:05 -04:00
bunnei
3f1f82a8c4 Merge pull request #1457 from ogniK5377/unmap-buffer
Unmapping an unmapped buffer should succeed
2018-10-08 01:20:18 -04:00
bunnei
ae982a9bdf Merge pull request #1419 from DarkLordZach/homebrew-args
nso/nro: Add support for passing command-line arguments to executable
2018-10-08 01:19:39 -04:00
David Marcec
c5c184246d Unmapping an unmapped buffer should succeed
Hardware tests show that trying to unmap an unmapped buffer already should always succeed. Hardware test was tested up to 32 iterations of attempting to unmap
2018-10-08 13:26:48 +11:00
Zach Hilman
f945e9767c nso/nro: Use default allocation size for arg_data 2018-10-07 14:32:33 -04:00
Zach Hilman
081f5c1dbf cmd: Support passing game arguments from command line
Uses -p (--program) and following string as args.
2018-10-07 14:32:32 -04:00
Zach Hilman
8bbc12b9c2 qt: Add UI option to configure arguments 2018-10-07 14:32:06 -04:00
Zach Hilman
95dff555a4 settings: Add program_args string setting 2018-10-07 14:32:05 -04:00
Zach Hilman
e09505ff61 nso/nro: Add NSO arguments structure to data section
Only added if arguments string is non-empty and a pass is requested by loader.
2018-10-07 14:30:15 -04:00
David Marcec
fa3f3cd07f Fixed assertion due to CountAddOnContent
Word count should be 3 since we're pushing a result code and a u32.

Also fixed up compiler warnings due to casting
2018-10-08 00:25:46 +11:00
bunnei
6e4d2e672d Merge pull request #1396 from DarkLordZach/packed-updates
loader: Add support for packed updates
2018-10-06 23:58:24 -04:00
bunnei
2c0b0ad50d Merge pull request #1446 from bunnei/fast_fermi_copy
gl_rasterizer: Implement accelerated Fermi2D copies.
2018-10-06 23:18:52 -04:00
bunnei
1cc5e6e9bc Merge pull request #1437 from FernandoS27/tex-mode2
Implemented Depth Compare, Shadow Samplers and Texture Processing Modes for TEXS and TLDS
2018-10-06 23:14:27 -04:00
bunnei
6f420a40cf Merge pull request #1453 from FearlessTobi/port-4311
Port citra-emu/citra#4311: "Remove "#" in the version number"
2018-10-06 23:12:58 -04:00
bunnei
44a3baf410 Merge pull request #1451 from FearlessTobi/port-4140
Port citra-emu/citra#4140: "misc input tab improvements"
2018-10-06 23:11:32 -04:00
bunnei
2fbb20b2b5 yuzu/yuzu_cmd: Add checks for required extension ARB_copy_image. 2018-10-06 12:06:40 -04:00
FernandoS27
752faff2bc Implemented Depth Compare and Shadow Samplers 2018-10-06 11:27:54 -04:00
fearlessTobi
8e6311bfd2 Remove "#" in the version number
So that people can stop using it in issue/pr comments and randomly link some other issue/pr unintentionally.
2018-10-06 15:51:37 +02:00
zhupengfei
690f326613 citra_qt/configuration: misc input tab improvements
* Added a context menu on the buttons including Clear & Restore Default

* Allow clearing (unsetting) inputs. Added a Clear All button

* Allow restoring a single input to default (instead of all)
2018-10-06 15:43:49 +02:00
bunnei
9aec85d39c fermi_2d: Implement simple copies with AccelerateSurfaceCopy. 2018-10-06 03:20:04 -04:00
bunnei
011cf77796 gl_rasterizer: Add rasterizer cache code to handle accerated fermi copies. 2018-10-06 03:20:04 -04:00
bunnei
749aef3dd0 gl_rasterizer_cache: Implement a simpler surface copy using glCopyImageSubData. 2018-10-06 03:20:04 -04:00
Zach Hilman
38c2ac95af romfs_factory: Extract packed update setter to new function 2018-10-05 08:53:51 -04:00
Zach Hilman
5acaeb04c4 patch_manager: Add support for NSP packed updates
Reads as Update (NSP) in add-ons
2018-10-05 08:48:44 -04:00
Zach Hilman
cf7aba4817 game_list: Add XCI update versioning to game list 2018-10-05 08:47:55 -04:00
Zach Hilman
d79d4fd764 patch_manager: Add support for packed updates
Will prefer any installed update over the packed version.
2018-10-05 08:47:24 -04:00
Zach Hilman
5045748829 loader: Add getter for packed update
Reads the update included with the game if it has one and adds the new ErrorNoPackedUpdate status.
2018-10-05 08:46:31 -04:00
Zach Hilman
e948fbf5d0 loader: Add ReadRomFSIVFCOffset to NSP, XCI, and NAX loaders
Fixes errors with certain updates
2018-10-05 08:46:31 -04:00
Zach Hilman
d0e6b93695 patch_manager: Avoid romfs_ext requirement for patching 2018-10-04 14:09:11 -04:00
Zach Hilman
c1e069c066 fsmitm_romfsbuild: Extract stubs and IPS to romfs_ext dir 2018-10-04 12:29:20 -04:00
Zach Hilman
bc4bec8a60 fsmitm_romfsbuild: Add support for stubbing and IPS patches in LFS 2018-10-04 12:29:14 -04:00
Zach Hilman
110d578470 ips_layer: Fix inaccuracies with comments and flags
Specifically bugs/crashes that arise when putting them in positions that are legal but not typical, such as midline, between patch data, or between patch records.
2018-10-04 12:23:27 -04:00
Zach Hilman
70bd2bb1d3 ips_layer: Deduplicate resource usage 2018-10-04 11:34:36 -04:00
Zach Hilman
9669cdb710 ips_layer: Add support for escape sequences and midline comments
More accurately follows IPSwitch specification.
2018-10-04 11:34:30 -04:00
Zach Hilman
8886f2e55e patch_manager: Add support for IPSwitch format patches 2018-10-04 11:34:06 -04:00
Zach Hilman
306739c2c4 ips_layer: Add IPSwitchCompiler to process IPSwitch format 2018-10-04 11:32:10 -04:00
Zach Hilman
f62227aa95 hex_util: Add HexVectorToString and HexStringToVector
Converts between bytes and strings when the size is not known at compile time.
2018-10-04 11:32:04 -04:00
FernandoS27
f664437ae8 Implemented Texture Processing Modes in TEXS and TLDS 2018-10-03 08:41:12 -04:00
58 changed files with 1112 additions and 205 deletions

View File

@@ -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()

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -73,27 +73,38 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
}
static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) {
std::vector<VirtualFile> ips;
ips.reserve(patch_dirs.size());
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) {
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() != "ips")
continue;
auto name = file->GetName();
const auto p1 = name.substr(0, name.find('.'));
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
if (file->GetExtension() == "ips") {
auto name = file->GetName();
const auto p1 = name.substr(0, name.find('.'));
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
if (build_id == this_build_id)
ips.push_back(file);
if (build_id == this_build_id)
out.push_back(file);
} else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
this_build_id =
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
if (build_id == this_build_id)
out.push_back(file);
}
}
}
}
return ips;
return out;
}
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
@@ -115,15 +126,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
const auto ips = CollectIPSPatches(patch_dirs, build_id);
const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso;
for (const auto& ips_file : ips) {
LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
if (patched != nullptr)
out = patched->ReadAllBytes();
for (const auto& patch_file : patches) {
if (patch_file->GetExtension() == "ips") {
LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
if (patched != nullptr)
out = patched->ReadAllBytes();
} else if (patch_file->GetExtension() == "pchtxt") {
LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const IPSwitchCompiler compiler{patch_file};
const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
if (patched != nullptr)
out = patched->ReadAllBytes();
}
}
if (out.size() < 0x100)
@@ -143,7 +163,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
return !CollectIPSPatches(patch_dirs, build_id).empty();
return !CollectPatches(patch_dirs, build_id).empty();
}
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
@@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
std::vector<VirtualDir> layers;
std::vector<VirtualDir> layers_ext;
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
auto ext_dir = subdir->GetSubdirectory("romfs_ext");
if (ext_dir != nullptr)
layers_ext.push_back(std::move(ext_dir));
}
layers.push_back(std::move(extracted));
@@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
auto packed = CreateRomFS(std::move(layered));
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
if (packed == nullptr) {
return;
}
@@ -184,8 +212,8 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
romfs = std::move(packed);
}
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
ContentRecordType type) const {
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
static_cast<u8>(type));
@@ -205,6 +233,13 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (update_raw != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
romfs = new_nca->GetRomFS();
}
}
// LayeredFS
@@ -224,7 +259,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
@@ -245,6 +281,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
"Update",
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
}
} else if (update_raw != nullptr) {
out.insert_or_assign("Update", "PACKED");
}
}
@@ -253,8 +291,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
AppendCommaIfNotEmpty(types, "IPS");
const auto exefs_dir = mod->GetSubdirectory("exefs");
if (IsDirValidAndNonEmpty(exefs_dir)) {
bool ips = false;
bool ipswitch = false;
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips")
ips = true;
else if (file->GetExtension() == "pchtxt")
ipswitch = true;
}
if (ips)
AppendCommaIfNotEmpty(types, "IPS");
if (ipswitch)
AppendCommaIfNotEmpty(types, "IPSwitch");
}
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");

View File

@@ -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.

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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(), [&current](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(),
[&current](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);

View File

@@ -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");

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -155,6 +155,7 @@ struct Values {
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
std::string program_args;
// WebService
bool enable_telemetry;

View File

@@ -2,12 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include <array>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -28,11 +32,11 @@ static u64 GenerateTelemetryId() {
mbedtls_entropy_context entropy;
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_context ctr_drbg;
std::string personalization = "yuzu Telemetry ID";
constexpr std::array<char, 18> personalization{{"yuzu Telemetry ID"}};
mbedtls_ctr_drbg_init(&ctr_drbg);
ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
reinterpret_cast<const unsigned char*>(personalization.c_str()),
reinterpret_cast<const unsigned char*>(personalization.data()),
personalization.size()) == 0);
ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
sizeof(u64)) == 0);

View File

@@ -5,6 +5,7 @@
#pragma once
#include <memory>
#include <string>
#include "common/telemetry.h"
namespace Core {
@@ -30,8 +31,6 @@ public:
field_collection.AddField(type, name, std::move(value));
}
static void FinalizeAsyncJob();
private:
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
@@ -53,7 +52,6 @@ u64 RegenerateTelemetryId();
* Verifies the username and token.
* @param username yuzu username to use for authentication.
* @param token yuzu token to use for authentication.
* @param func A function that gets exectued when the verification is finished
* @returns Future with bool indicating whether the verification succeeded
*/
bool VerifyLogin(const std::string& username, const std::string& token);

View File

@@ -4,11 +4,13 @@
#include "core/memory.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
: memory_manager(memory_manager), rasterizer{rasterizer} {}
void Fermi2D::WriteReg(u32 method, u32 value) {
ASSERT_MSG(method < Regs::NUM_REGS,
@@ -44,27 +46,31 @@ void Fermi2D::HandleSurfaceCopy() {
u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
if (regs.src.linear == regs.dst.linear) {
// If the input layout and the output layout are the same, just perform a raw copy.
ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
Memory::CopyBlock(dest_cpu, source_cpu,
src_bytes_per_pixel * regs.dst.width * regs.dst.height);
return;
}
if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) {
// TODO(bunnei): The below implementation currently will not get hit, as
// AccelerateSurfaceCopy tries to always copy and will always return success. This should be
// changed once we properly support flushing.
u8* src_buffer = Memory::GetPointer(source_cpu);
u8* dst_buffer = Memory::GetPointer(dest_cpu);
if (!regs.src.linear && regs.dst.linear) {
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, src_buffer, dst_buffer, true,
regs.src.BlockHeight());
} else {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, dst_buffer, src_buffer, false,
regs.dst.BlockHeight());
if (regs.src.linear == regs.dst.linear) {
// If the input layout and the output layout are the same, just perform a raw copy.
ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
Memory::CopyBlock(dest_cpu, source_cpu,
src_bytes_per_pixel * regs.dst.width * regs.dst.height);
return;
}
u8* src_buffer = Memory::GetPointer(source_cpu);
u8* dst_buffer = Memory::GetPointer(dest_cpu);
if (!regs.src.linear && regs.dst.linear) {
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, src_buffer, dst_buffer, true,
regs.src.BlockHeight());
} else {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
dst_bytes_per_pixel, dst_buffer, src_buffer, false,
regs.dst.BlockHeight());
}
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -617,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;
}
@@ -662,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) {
@@ -693,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,

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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);

View File

@@ -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");
const bool depth_compare =
instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
auto texture_type = instr.tld4.texture_type.Value();
u32 num_coordinates = TextureCoordinates(texture_type);
if (depth_compare)
num_coordinates += 1;
switch (instr.tld4.texture_type) {
case Tegra::Shader::TextureType::Texture2D: {
switch (num_coordinates) {
case 2: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
break;
}
case 3: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
break;
}
default:
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
static_cast<u32>(instr.tld4.texture_type.Value()));
LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
static_cast<u32>(num_coordinates));
UNREACHABLE();
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
texture_type = Tegra::Shader::TextureType::Texture2D;
}
const std::string sampler =
GetSampler(instr.sampler, instr.tld4.texture_type, false);
GetSampler(instr.sampler, texture_type, false, depth_compare);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
shader.AddLine("{");
@@ -2106,15 +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.

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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;

View File

@@ -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");

View File

@@ -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);

View File

@@ -106,6 +106,29 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Homebrew</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Arguments String</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="homebrew_args_edit"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@@ -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() {

View File

@@ -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();

View File

@@ -694,6 +694,34 @@ Capture:</string>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonClearAll">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Clear All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="sizePolicy">

View File

@@ -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)),

View File

@@ -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)

View File

@@ -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 =

View File

@@ -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)

View File

@@ -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