Compare commits
81 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73a2d71f44 | ||
|
|
bd8065295c | ||
|
|
6d64ecf359 | ||
|
|
5ff72a48a7 | ||
|
|
af074ee422 | ||
|
|
deff28d3c0 | ||
|
|
3d9776f36a | ||
|
|
a76f0d5d06 | ||
|
|
4048b54ef7 | ||
|
|
9cd79c25ed | ||
|
|
2515d2433b | ||
|
|
8b08cb925b | ||
|
|
a8974f0556 | ||
|
|
23ae7cf9db | ||
|
|
fdd5c97a14 | ||
|
|
f165a85398 | ||
|
|
0731383124 | ||
|
|
05f6f59ffb | ||
|
|
ce8291f6c5 | ||
|
|
9dccf7e1fa | ||
|
|
030676b95d | ||
|
|
a439f7b6e1 | ||
|
|
b56e5edafc | ||
|
|
460ebc8187 | ||
|
|
6ac1bd9f5d | ||
|
|
7e9b79955f | ||
|
|
564b7fdc9c | ||
|
|
c08c5d346a | ||
|
|
9382414b20 | ||
|
|
e3af341d5b | ||
|
|
3f17fe7133 | ||
|
|
a164b413fa | ||
|
|
9273c02427 | ||
|
|
b89dda2b98 | ||
|
|
9947c6ad59 | ||
|
|
9b50dca2bb | ||
|
|
009a2cc9cc | ||
|
|
6faf1b0972 | ||
|
|
820f646458 | ||
|
|
948f6c0738 | ||
|
|
ddcdbce067 | ||
|
|
8d685a29bc | ||
|
|
14230fe2af | ||
|
|
68296d9474 | ||
|
|
8f4e09ba07 | ||
|
|
56ab608044 | ||
|
|
54724fe918 | ||
|
|
b155b3ef81 | ||
|
|
a859a35ec8 | ||
|
|
fbaefc47a0 | ||
|
|
742f895f8b | ||
|
|
a781042700 | ||
|
|
77554ac773 | ||
|
|
6f09c5b128 | ||
|
|
e63b229f4a | ||
|
|
94f193af65 | ||
|
|
a6ae765410 | ||
|
|
aba988f71c | ||
|
|
6bd6beee20 | ||
|
|
d3ad9469a1 | ||
|
|
c0b7ed8b58 | ||
|
|
c913136eb2 | ||
|
|
7d5d781b20 | ||
|
|
23a16c1720 | ||
|
|
92e26df00f | ||
|
|
c91b60a421 | ||
|
|
cbd517d8cc | ||
|
|
2814ca3624 | ||
|
|
a6e75cd45b | ||
|
|
9664ce255d | ||
|
|
f92b3512e0 | ||
|
|
8e150c46b9 | ||
|
|
f5e03b9173 | ||
|
|
08fcb4694f | ||
|
|
97bf83bc56 | ||
|
|
8e900a301a | ||
|
|
54e7ddb93a | ||
|
|
1efe5a76b1 | ||
|
|
9951f6d054 | ||
|
|
d2caf4af7d | ||
|
|
99fbcb3bf2 |
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 0435ac2d80...959446573f
2
externals/xbyak
vendored
2
externals/xbyak
vendored
Submodule externals/xbyak updated: 71b75f653f...1de435ed04
@@ -17,10 +17,10 @@ AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||
|
||||
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_core->StartStream(stream);
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_out->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
QueueMixedBuffer(1);
|
||||
@@ -236,11 +236,11 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
}
|
||||
}
|
||||
}
|
||||
audio_core->QueueBuffer(stream, tag, std::move(buffer));
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer));
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
for (const auto& tag : released_buffers) {
|
||||
QueueMixedBuffer(tag);
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ private:
|
||||
AudioRendererParameter worker_params;
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_out;
|
||||
AudioCore::StreamPtr stream;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Bleeding Edge) to the scm_rev file as well
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
if ($ENV{CI})
|
||||
if ($ENV{TRAVIS})
|
||||
set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG})
|
||||
set(BUILD_TAG $ENV{TRAVIS_TAG})
|
||||
elseif($ENV{APPVEYOR})
|
||||
set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
|
||||
set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
|
||||
endif()
|
||||
# regex capture the string nightly or bleeding-edge into CMAKE_MATCH_1
|
||||
# regex capture the string nightly or canary into CMAKE_MATCH_1
|
||||
string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
# capitalize the first letter of each word in the repo name.
|
||||
@@ -16,10 +19,21 @@ if ($ENV{CI})
|
||||
string(SUBSTRING ${WORD} 0 1 FIRST_LETTER)
|
||||
string(SUBSTRING ${WORD} 1 -1 REMAINDER)
|
||||
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
|
||||
# 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(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ")
|
||||
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}")
|
||||
endforeach()
|
||||
if (BUILD_TAG)
|
||||
string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
set(BUILD_VERSION ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
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} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY)
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#define GIT_DESC "@GIT_DESC@"
|
||||
#define BUILD_NAME "@REPO_NAME@"
|
||||
#define BUILD_DATE "@BUILD_DATE@"
|
||||
#define BUILD_FULLNAME "@BUILD_FULLNAME@"
|
||||
#define BUILD_VERSION "@BUILD_VERSION@"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -17,6 +19,8 @@ const char g_scm_branch[] = GIT_BRANCH;
|
||||
const char g_scm_desc[] = GIT_DESC;
|
||||
const char g_build_name[] = BUILD_NAME;
|
||||
const char g_build_date[] = BUILD_DATE;
|
||||
const char g_build_fullname[] = BUILD_FULLNAME;
|
||||
const char g_build_version[] = BUILD_VERSION;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -11,5 +11,7 @@ extern const char g_scm_branch[];
|
||||
extern const char g_scm_desc[];
|
||||
extern const char g_build_name[];
|
||||
extern const char g_build_date[];
|
||||
extern const char g_build_fullname[];
|
||||
extern const char g_build_version[];
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -35,8 +35,12 @@ add_library(core STATIC
|
||||
file_sys/mode.h
|
||||
file_sys/nca_metadata.cpp
|
||||
file_sys/nca_metadata.h
|
||||
file_sys/nca_patch.cpp
|
||||
file_sys/nca_patch.h
|
||||
file_sys/partition_filesystem.cpp
|
||||
file_sys/partition_filesystem.h
|
||||
file_sys/patch_manager.cpp
|
||||
file_sys/patch_manager.h
|
||||
file_sys/program_metadata.cpp
|
||||
file_sys/program_metadata.h
|
||||
file_sys/registered_cache.cpp
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
@@ -137,7 +136,7 @@ struct System::Impl {
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
|
||||
current_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
|
||||
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
||||
@@ -203,7 +202,7 @@ struct System::Impl {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
|
||||
const Loader::ResultStatus load_result{app_loader->Load(kernel.CurrentProcess())};
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
Shutdown();
|
||||
@@ -282,7 +281,6 @@ struct System::Impl {
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
@@ -364,7 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
ARM_Interface& System::ArmInterface(size_t core_index) {
|
||||
|
||||
@@ -174,9 +174,12 @@ public:
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
|
||||
|
||||
/// Gets the current process
|
||||
/// Provides a reference to the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
|
||||
|
||||
/// Provides a constant reference to the current process.
|
||||
const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
Kernel::KernelCore& Kernel();
|
||||
|
||||
|
||||
@@ -82,11 +82,25 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
|
||||
}
|
||||
} else {
|
||||
const auto block_size = mbedtls_cipher_get_block_size(context);
|
||||
if (size < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src, size);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest, block.data(), size);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += block_size) {
|
||||
auto length = std::min<size_t>(block_size, size - offset);
|
||||
mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
|
||||
if (written != length) {
|
||||
if (length < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src + offset, length);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest + offset, block.data(), length);
|
||||
return;
|
||||
}
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
length, written);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
UpdateIV(base_offset + offset);
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
|
||||
return raw.size();
|
||||
return length;
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x10)
|
||||
|
||||
@@ -52,11 +52,11 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
const auto secure_ncas = secure_partition->GetNCAsCollapsed();
|
||||
std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
|
||||
|
||||
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
program =
|
||||
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
|
||||
if (program != nullptr)
|
||||
program_nca_status = program->GetStatus();
|
||||
program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
|
||||
if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA)
|
||||
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
|
||||
auto result = AddNCAFromPartition(XCIPartition::Update);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_patch.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
@@ -68,10 +69,31 @@ struct RomFSSuperblock {
|
||||
};
|
||||
static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
|
||||
|
||||
struct BKTRHeader {
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le number_entries;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
|
||||
|
||||
struct BKTRSuperblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
IVFCHeader ivfc;
|
||||
INSERT_PADDING_BYTES(0x18);
|
||||
BKTRHeader relocation;
|
||||
BKTRHeader subsection;
|
||||
INSERT_PADDING_BYTES(0xC0);
|
||||
};
|
||||
static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
|
||||
|
||||
union NCASectionHeader {
|
||||
NCASectionRaw raw;
|
||||
PFS0Superblock pfs0;
|
||||
RomFSSuperblock romfs;
|
||||
BKTRSuperblock bktr;
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
|
||||
|
||||
@@ -104,7 +126,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
|
||||
Core::Crypto::Key128 out;
|
||||
if (type == NCASectionCryptoType::XTS)
|
||||
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
|
||||
else if (type == NCASectionCryptoType::CTR)
|
||||
else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
|
||||
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
|
||||
else
|
||||
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
|
||||
@@ -154,6 +176,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
LOG_DEBUG(Crypto, "called with mode=NONE");
|
||||
return in;
|
||||
case NCASectionCryptoType::CTR:
|
||||
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
|
||||
// which uses the same CTR as usual.
|
||||
case NCASectionCryptoType::BKTR:
|
||||
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
|
||||
{
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
@@ -190,7 +215,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
}
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
||||
: file(std::move(file_)),
|
||||
bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
|
||||
if (file == nullptr) {
|
||||
@@ -265,22 +292,21 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
}) != sections.end();
|
||||
ivfc_offset = 0;
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
auto section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
const size_t romfs_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
|
||||
section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t base_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t romfs_offset = base_offset + ivfc_offset;
|
||||
const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
|
||||
romfs_offset);
|
||||
if (dec != nullptr) {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
|
||||
auto dec = Decrypt(section, raw, romfs_offset);
|
||||
|
||||
if (dec == nullptr) {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
@@ -289,6 +315,117 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
|
||||
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
|
||||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
|
||||
status = Loader::ResultStatus::ErrorBadBKTRHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
|
||||
section.bktr.subsection.offset) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 size =
|
||||
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
RelocationBlock relocation_block{};
|
||||
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBlock;
|
||||
return;
|
||||
}
|
||||
SubsectionBlock subsection_block{};
|
||||
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucketRaw> relocation_buckets_raw(
|
||||
(section.bktr.relocation.size - sizeof(RelocationBlock)) /
|
||||
sizeof(RelocationBucketRaw));
|
||||
if (dec->ReadBytes(relocation_buckets_raw.data(),
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock),
|
||||
section.bktr.relocation.offset + sizeof(RelocationBlock) -
|
||||
offset) !=
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
|
||||
(section.bktr.subsection.size - sizeof(SubsectionBlock)) /
|
||||
sizeof(SubsectionBucketRaw));
|
||||
if (dec->ReadBytes(subsection_buckets_raw.data(),
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock),
|
||||
section.bktr.subsection.offset + sizeof(SubsectionBlock) -
|
||||
offset) !=
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
|
||||
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
|
||||
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
|
||||
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
|
||||
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
|
||||
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
|
||||
|
||||
u32 ctr_low;
|
||||
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
|
||||
subsection_buckets.back().entries.push_back(
|
||||
{section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bktr_base_romfs == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
return;
|
||||
}
|
||||
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets,
|
||||
encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
|
||||
bktr_base_ivfc_offset, section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
|
||||
files.push_back(std::make_shared<OffsetVfsFile>(
|
||||
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
@@ -304,6 +441,12 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
@@ -349,11 +492,15 @@ NCAContentType NCA::GetType() const {
|
||||
}
|
||||
|
||||
u64 NCA::GetTitleId() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
|
||||
return header.title_id | 0x800;
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetRomFS() const {
|
||||
return romfs;
|
||||
}
|
||||
@@ -366,8 +513,8 @@ VirtualFile NCA::GetBaseFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
u64 NCA::GetBaseIVFCOffset() const {
|
||||
return ivfc_offset;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
|
||||
@@ -79,7 +79,8 @@ bool IsValidNCA(const NCAHeader& header);
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NCA(VirtualFile file);
|
||||
explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
|
||||
u64 bktr_base_ivfc_offset = 0);
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
@@ -89,13 +90,15 @@ public:
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
bool IsUpdate() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
VirtualFile GetBaseFile() const;
|
||||
|
||||
bool IsUpdate() const;
|
||||
// Returns the base ivfc offset used in BKTR patching.
|
||||
u64 GetBaseIVFCOffset() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
@@ -112,14 +115,16 @@ private:
|
||||
VirtualFile romfs = nullptr;
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualFile file;
|
||||
VirtualFile bktr_base_romfs;
|
||||
u64 ivfc_offset;
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
bool is_update{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted;
|
||||
bool is_update;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
210
src/core/file_sys/nca_patch.cpp
Normal file
210
src/core/file_sys/nca_patch.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/file_sys/nca_patch.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
|
||||
std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
|
||||
std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
|
||||
Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
|
||||
std::array<u8, 8> section_ctr_)
|
||||
: relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
|
||||
subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
|
||||
base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
|
||||
encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
|
||||
section_ctr(section_ctr_) {
|
||||
for (size_t i = 0; i < relocation.number_buckets - 1; ++i) {
|
||||
relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < subsection.number_buckets - 1; ++i) {
|
||||
subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
|
||||
{0},
|
||||
subsection_buckets[i + 1].entries[0].ctr});
|
||||
}
|
||||
|
||||
relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
|
||||
}
|
||||
|
||||
BKTR::~BKTR() = default;
|
||||
|
||||
size_t BKTR::Read(u8* data, size_t length, size_t offset) const {
|
||||
// Read out of bounds.
|
||||
if (offset >= relocation.size)
|
||||
return 0;
|
||||
const auto relocation = GetRelocationEntry(offset);
|
||||
const auto section_offset = offset - relocation.address_patch + relocation.address_source;
|
||||
const auto bktr_read = relocation.from_patch;
|
||||
|
||||
const auto next_relocation = GetNextRelocationEntry(offset);
|
||||
|
||||
if (offset + length > next_relocation.address_patch) {
|
||||
const u64 partition = next_relocation.address_patch - offset;
|
||||
return Read(data, partition, offset) +
|
||||
Read(data + partition, length - partition, offset + partition);
|
||||
}
|
||||
|
||||
if (!bktr_read) {
|
||||
ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative.");
|
||||
return base_romfs->Read(data, length, section_offset - ivfc_offset);
|
||||
}
|
||||
|
||||
if (!encrypted) {
|
||||
return bktr_romfs->Read(data, length, section_offset);
|
||||
}
|
||||
|
||||
const auto subsection = GetSubsectionEntry(section_offset);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
|
||||
|
||||
// Calculate AES IV
|
||||
std::vector<u8> iv(16);
|
||||
auto subsection_ctr = subsection.ctr;
|
||||
auto offset_iv = section_offset + base_offset;
|
||||
for (size_t i = 0; i < section_ctr.size(); ++i)
|
||||
iv[i] = section_ctr[0x8 - i - 1];
|
||||
offset_iv >>= 4;
|
||||
for (size_t i = 0; i < sizeof(u64); ++i) {
|
||||
iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
|
||||
offset_iv >>= 8;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(u32); ++i) {
|
||||
iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
|
||||
subsection_ctr >>= 8;
|
||||
}
|
||||
cipher.SetIV(iv);
|
||||
|
||||
const auto next_subsection = GetNextSubsectionEntry(section_offset);
|
||||
|
||||
if (section_offset + length > next_subsection.address_patch) {
|
||||
const u64 partition = next_subsection.address_patch - section_offset;
|
||||
return Read(data, partition, offset) +
|
||||
Read(data + partition, length - partition, offset + partition);
|
||||
}
|
||||
|
||||
const auto block_offset = section_offset & 0xF;
|
||||
if (block_offset != 0) {
|
||||
auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
|
||||
cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt);
|
||||
if (length + block_offset < 0x10) {
|
||||
std::memcpy(data, block.data() + block_offset, std::min(length, block.size()));
|
||||
return std::min(length, block.size());
|
||||
}
|
||||
|
||||
const auto read = 0x10 - block_offset;
|
||||
std::memcpy(data, block.data() + block_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
}
|
||||
|
||||
const auto raw_read = bktr_romfs->Read(data, length, section_offset);
|
||||
cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
|
||||
return raw_read;
|
||||
}
|
||||
|
||||
template <bool Subsection, typename BlockType, typename BucketType>
|
||||
std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
|
||||
BucketType buckets) const {
|
||||
if constexpr (Subsection) {
|
||||
const auto last_bucket = buckets[block.number_buckets - 1];
|
||||
if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
|
||||
return {block.number_buckets - 1, last_bucket.number_entries};
|
||||
} else {
|
||||
ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
|
||||
}
|
||||
|
||||
size_t bucket_id = std::count_if(block.base_offsets.begin() + 1,
|
||||
block.base_offsets.begin() + block.number_buckets,
|
||||
[&offset](u64 base_offset) { return base_offset <= offset; });
|
||||
|
||||
const auto bucket = buckets[bucket_id];
|
||||
|
||||
if (bucket.number_entries == 1)
|
||||
return {bucket_id, 0};
|
||||
|
||||
size_t low = 0;
|
||||
size_t mid = 0;
|
||||
size_t high = bucket.number_entries - 1;
|
||||
while (low <= high) {
|
||||
mid = (low + high) / 2;
|
||||
if (bucket.entries[mid].address_patch > offset) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
if (mid == bucket.number_entries - 1 ||
|
||||
bucket.entries[mid + 1].address_patch > offset) {
|
||||
return {bucket_id, mid};
|
||||
}
|
||||
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
UNREACHABLE_MSG("Offset could not be found in BKTR block.");
|
||||
}
|
||||
|
||||
RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
|
||||
return relocation_buckets[res.first].entries[res.second];
|
||||
}
|
||||
|
||||
RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
|
||||
const auto bucket = relocation_buckets[res.first];
|
||||
if (res.second + 1 < bucket.entries.size())
|
||||
return bucket.entries[res.second + 1];
|
||||
return relocation_buckets[res.first + 1].entries[0];
|
||||
}
|
||||
|
||||
SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
|
||||
return subsection_buckets[res.first].entries[res.second];
|
||||
}
|
||||
|
||||
SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
|
||||
const auto bucket = subsection_buckets[res.first];
|
||||
if (res.second + 1 < bucket.entries.size())
|
||||
return bucket.entries[res.second + 1];
|
||||
return subsection_buckets[res.first + 1].entries[0];
|
||||
}
|
||||
|
||||
std::string BKTR::GetName() const {
|
||||
return base_romfs->GetName();
|
||||
}
|
||||
|
||||
size_t BKTR::GetSize() const {
|
||||
return relocation.size;
|
||||
}
|
||||
|
||||
bool BKTR::Resize(size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
|
||||
return base_romfs->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool BKTR::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BKTR::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t BKTR::Write(const u8* data, size_t length, size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BKTR::Rename(std::string_view name) {
|
||||
return base_romfs->Rename(name);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
150
src/core/file_sys/nca_patch.h
Normal file
150
src/core/file_sys/nca_patch.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct RelocationEntry {
|
||||
u64_le address_patch;
|
||||
u64_le address_source;
|
||||
u32 from_patch;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
|
||||
|
||||
struct RelocationBucketRaw {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_entries;
|
||||
u64_le end_offset;
|
||||
std::array<RelocationEntry, 0x332> relocation_entries;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
};
|
||||
static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
|
||||
|
||||
// Vector version of RelocationBucketRaw
|
||||
struct RelocationBucket {
|
||||
u32 number_entries;
|
||||
u64 end_offset;
|
||||
std::vector<RelocationEntry> entries;
|
||||
};
|
||||
|
||||
struct RelocationBlock {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_buckets;
|
||||
u64_le size;
|
||||
std::array<u64, 0x7FE> base_offsets;
|
||||
};
|
||||
static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
|
||||
|
||||
struct SubsectionEntry {
|
||||
u64_le address_patch;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le ctr;
|
||||
};
|
||||
static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
|
||||
|
||||
struct SubsectionBucketRaw {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_entries;
|
||||
u64_le end_offset;
|
||||
std::array<SubsectionEntry, 0x3FF> subsection_entries;
|
||||
};
|
||||
static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
|
||||
|
||||
// Vector version of SubsectionBucketRaw
|
||||
struct SubsectionBucket {
|
||||
u32 number_entries;
|
||||
u64 end_offset;
|
||||
std::vector<SubsectionEntry> entries;
|
||||
};
|
||||
|
||||
struct SubsectionBlock {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_buckets;
|
||||
u64_le size;
|
||||
std::array<u64, 0x7FE> base_offsets;
|
||||
};
|
||||
static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
|
||||
|
||||
inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
|
||||
return {raw.number_entries,
|
||||
raw.end_offset,
|
||||
{raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
|
||||
}
|
||||
|
||||
inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
|
||||
return {raw.number_entries,
|
||||
raw.end_offset,
|
||||
{raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
|
||||
}
|
||||
|
||||
class BKTR : public VfsFile {
|
||||
public:
|
||||
BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
|
||||
std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
|
||||
std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
|
||||
Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
|
||||
~BKTR() override;
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
size_t GetSize() const override;
|
||||
|
||||
bool Resize(size_t new_size) override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
|
||||
bool IsWritable() const override;
|
||||
|
||||
bool IsReadable() const override;
|
||||
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
private:
|
||||
template <bool Subsection, typename BlockType, typename BucketType>
|
||||
std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block,
|
||||
BucketType buckets) const;
|
||||
|
||||
RelocationEntry GetRelocationEntry(u64 offset) const;
|
||||
RelocationEntry GetNextRelocationEntry(u64 offset) const;
|
||||
|
||||
SubsectionEntry GetSubsectionEntry(u64 offset) const;
|
||||
SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
|
||||
|
||||
RelocationBlock relocation;
|
||||
std::vector<RelocationBucket> relocation_buckets;
|
||||
SubsectionBlock subsection;
|
||||
std::vector<SubsectionBucket> subsection_buckets;
|
||||
|
||||
// Should be the raw base romfs, decrypted.
|
||||
VirtualFile base_romfs;
|
||||
// Should be the raw BKTR romfs, (located at media_offset with size media_size).
|
||||
VirtualFile bktr_romfs;
|
||||
|
||||
bool encrypted;
|
||||
Core::Crypto::Key128 key;
|
||||
|
||||
// Base offset into NCA, used for IV calculation.
|
||||
u64 base_offset;
|
||||
// Distance between IVFC start and RomFS start, used for base reads
|
||||
u64 ivfc_offset;
|
||||
std::array<u8, 8> section_ctr;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
157
src/core/file_sys/patch_manager.cpp
Normal file
157
src/core/file_sys/patch_manager.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#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/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
||||
for (size_t i = 1; i < bytes.size(); ++i) {
|
||||
version /= SINGLE_BYTE_MODULUS;
|
||||
bytes[i] = version % SINGLE_BYTE_MODULUS;
|
||||
}
|
||||
|
||||
if (format == TitleVersionFormat::FourElements)
|
||||
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{
|
||||
"Update",
|
||||
};
|
||||
|
||||
std::string FormatPatchTypeName(PatchType type) {
|
||||
return PATCH_TYPE_NAMES.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
||||
|
||||
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
|
||||
|
||||
if (exefs == nullptr)
|
||||
return exefs;
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
|
||||
if (update != nullptr) {
|
||||
if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
update->GetExeFS() != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
}
|
||||
|
||||
return exefs;
|
||||
}
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
ContentRecordType type) const {
|
||||
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
|
||||
static_cast<u8>(type));
|
||||
|
||||
if (romfs == nullptr)
|
||||
return romfs;
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed->GetEntryRaw(update_tid, type);
|
||||
if (update != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
}
|
||||
|
||||
return romfs;
|
||||
}
|
||||
|
||||
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<PatchType, std::string> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid};
|
||||
auto [nacp, discard_icon_file] = update.GetControlMetadata();
|
||||
|
||||
if (nacp != nullptr) {
|
||||
out[PatchType::Update] = nacp->GetVersionString();
|
||||
} else {
|
||||
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = installed->GetEntryVersion(update_tid);
|
||||
if (meta_ver == boost::none || meta_ver.get() == 0) {
|
||||
out[PatchType::Update] = "";
|
||||
} else {
|
||||
out[PatchType::Update] =
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto& installed{Service::FileSystem::GetUnionContents()};
|
||||
|
||||
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr)
|
||||
return {};
|
||||
|
||||
return ParseControlNCA(base_control_nca);
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const {
|
||||
const auto base_romfs = nca->GetRomFS();
|
||||
if (base_romfs == nullptr)
|
||||
return {};
|
||||
|
||||
const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
|
||||
if (romfs == nullptr)
|
||||
return {};
|
||||
|
||||
const auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted == nullptr)
|
||||
return {};
|
||||
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (nacp_file == nullptr)
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
|
||||
const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
|
||||
|
||||
VirtualFile icon_file;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr)
|
||||
break;
|
||||
}
|
||||
|
||||
return {nacp, icon_file};
|
||||
}
|
||||
} // namespace FileSys
|
||||
63
src/core/file_sys/patch_manager.h
Normal file
63
src/core/file_sys/patch_manager.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
class NACP;
|
||||
|
||||
enum class TitleVersionFormat : u8 {
|
||||
ThreeElements, ///< vX.Y.Z
|
||||
FourElements, ///< vX.Y.Z.W
|
||||
};
|
||||
|
||||
std::string FormatTitleVersion(u32 version,
|
||||
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
|
||||
|
||||
enum class PatchType {
|
||||
Update,
|
||||
};
|
||||
|
||||
std::string FormatPatchTypeName(PatchType type);
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
class PatchManager {
|
||||
public:
|
||||
explicit PatchManager(u64 title_id);
|
||||
|
||||
// Currently tracked ExeFS patches:
|
||||
// - Game Updates
|
||||
VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
|
||||
ContentRecordType type = ContentRecordType::Program) const;
|
||||
|
||||
// Returns a vector of pairs between patch names and patch versions.
|
||||
// i.e. Update v80 will return {Update, 80}
|
||||
std::map<PatchType, std::string> GetPatchVersionNames() 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.
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
||||
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const;
|
||||
|
||||
private:
|
||||
u64 title_id;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -280,6 +280,18 @@ VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
const auto meta_iter = meta.find(title_id);
|
||||
if (meta_iter != meta.end())
|
||||
return meta_iter->second.GetTitleVersion();
|
||||
|
||||
const auto yuzu_meta_iter = yuzu_meta.find(title_id);
|
||||
if (yuzu_meta_iter != yuzu_meta.end())
|
||||
return yuzu_meta_iter->second.GetTitleVersion();
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
@@ -498,4 +510,107 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
kv.second.GetTitleID() == cnmt.GetTitleID();
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
|
||||
: caches(std::move(caches)) {}
|
||||
|
||||
void RegisteredCacheUnion::Refresh() {
|
||||
for (const auto& c : caches)
|
||||
c->Refresh();
|
||||
}
|
||||
|
||||
bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
|
||||
return cache->HasEntry(title_id, type);
|
||||
});
|
||||
}
|
||||
|
||||
bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryVersion(title_id);
|
||||
if (res != boost::none)
|
||||
return res;
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryUnparsed(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryRaw(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
|
||||
boost::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type != boost::none && title_type.get() != c.GetType())
|
||||
return false;
|
||||
if (record_type != boost::none && record_type.get() != r.type)
|
||||
return false;
|
||||
if (title_id != boost::none && title_id.get() != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -43,6 +43,10 @@ struct RegisteredCacheEntry {
|
||||
std::string DebugInfo() const;
|
||||
};
|
||||
|
||||
constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||
return base_title_id | 0x800;
|
||||
}
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
|
||||
@@ -60,6 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
|
||||
* 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache {
|
||||
friend class RegisteredCacheUnion;
|
||||
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
@@ -74,6 +80,8 @@ public:
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
boost::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
@@ -131,4 +139,36 @@ private:
|
||||
boost::container::flat_map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class RegisteredCacheUnion {
|
||||
public:
|
||||
explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
|
||||
|
||||
void Refresh();
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
boost::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type = boost::none,
|
||||
boost::optional<ContentRecordType> record_type = boost::none,
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<RegisteredCache>> caches;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.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/romfs_factory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@@ -19,10 +23,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
|
||||
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Service_FS, "Unable to read RomFS!");
|
||||
}
|
||||
|
||||
updatable = app_loader.IsRomFSUpdatable();
|
||||
ivfc_offset = app_loader.ReadRomFSIVFCOffset();
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
return MakeResult<VirtualFile>(file);
|
||||
if (!updatable)
|
||||
return MakeResult<VirtualFile>(file);
|
||||
|
||||
const PatchManager patch_manager(Core::CurrentProcess()->program_id);
|
||||
return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
bool updatable;
|
||||
u64 ivfc_offset;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
@@ -13,8 +19,8 @@
|
||||
|
||||
namespace FileSys {
|
||||
NSP::NSP(VirtualFile file_)
|
||||
: file(std::move(file_)),
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)), status{Loader::ResultStatus::Success} {
|
||||
: file(std::move(file_)), status{Loader::ResultStatus::Success},
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)) {
|
||||
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
|
||||
status = pfs->GetStatus();
|
||||
return;
|
||||
@@ -60,8 +66,11 @@ NSP::NSP(VirtualFile file_)
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
|
||||
const auto nca = std::make_shared<NCA>(outer_file);
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success)
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
program_status[nca->GetTitleId()] = nca->GetStatus();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& inner_file : section0->GetFiles()) {
|
||||
|
||||
@@ -4,20 +4,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
class PartitionFilesystem;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
|
||||
class NSP : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NSP(VirtualFile file);
|
||||
|
||||
@@ -116,6 +116,7 @@ struct KernelCore::Impl {
|
||||
next_thread_id = 1;
|
||||
|
||||
process_list.clear();
|
||||
current_process.reset();
|
||||
|
||||
handle_table.Clear();
|
||||
resource_limits.fill(nullptr);
|
||||
@@ -206,6 +207,7 @@ struct KernelCore::Impl {
|
||||
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
SharedPtr<Process> current_process;
|
||||
|
||||
Kernel::HandleTable handle_table;
|
||||
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
|
||||
@@ -264,6 +266,18 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
|
||||
void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
|
||||
impl->current_process = std::move(process);
|
||||
}
|
||||
|
||||
SharedPtr<Process>& KernelCore::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
const SharedPtr<Process>& KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
@@ -65,6 +65,15 @@ public:
|
||||
/// Adds the given shared pointer to an internal list of active processes.
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Makes the given process the new current process.
|
||||
void MakeCurrentProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Retrieves a reference to the current process.
|
||||
SharedPtr<Process>& CurrentProcess();
|
||||
|
||||
/// Retrieves a const reference to the current process.
|
||||
const SharedPtr<Process>& CurrentProcess() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
@@ -307,6 +308,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
return sdmc_factory->Open();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
|
||||
return std::make_shared<FileSys::RegisteredCacheUnion>(
|
||||
std::vector<std::shared_ptr<FileSys::RegisteredCache>>{
|
||||
GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Contents");
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
namespace FileSys {
|
||||
class BISFactory;
|
||||
class RegisteredCache;
|
||||
class RegisteredCacheUnion;
|
||||
class RomFSFactory;
|
||||
class SaveDataFactory;
|
||||
class SDMCFactory;
|
||||
@@ -45,6 +46,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
FileSys::SaveDataDescriptor save_struct);
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC();
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
|
||||
|
||||
@@ -57,4 +57,6 @@ Controller::Controller() : ServiceFramework("IpcController") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
Controller::~Controller() = default;
|
||||
|
||||
} // namespace Service::SM
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Service::SM {
|
||||
class Controller final : public ServiceFramework<Controller> {
|
||||
public:
|
||||
Controller();
|
||||
~Controller() = default;
|
||||
~Controller() override;
|
||||
|
||||
private:
|
||||
void ConvertSessionToDomain(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
namespace Service::SM {
|
||||
|
||||
ServiceManager::ServiceManager() = default;
|
||||
ServiceManager::~ServiceManager() = default;
|
||||
|
||||
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
|
||||
|
||||
@@ -46,6 +46,7 @@ class ServiceManager {
|
||||
public:
|
||||
static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
|
||||
|
||||
ServiceManager();
|
||||
~ServiceManager();
|
||||
|
||||
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/core.h"
|
||||
#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/romfs_factory.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -21,10 +22,19 @@
|
||||
|
||||
namespace Loader {
|
||||
|
||||
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_)
|
||||
: AppLoader(std::move(file_)) {
|
||||
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
|
||||
bool override_update)
|
||||
: AppLoader(std::move(file_)), override_update(override_update) {
|
||||
const auto dir = file->GetContainingDirectory();
|
||||
|
||||
// Title ID
|
||||
const auto npdm = dir->GetFile("main.npdm");
|
||||
if (npdm != nullptr) {
|
||||
const auto res = metadata.Load(npdm);
|
||||
if (res == ResultStatus::Success)
|
||||
title_id = metadata.GetTitleID();
|
||||
}
|
||||
|
||||
// Icon
|
||||
FileSys::VirtualFile icon_file = nullptr;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
@@ -66,8 +76,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
|
||||
}
|
||||
|
||||
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
|
||||
FileSys::VirtualDir directory)
|
||||
: AppLoader(directory->GetFile("main")), dir(std::move(directory)) {}
|
||||
FileSys::VirtualDir directory, bool override_update)
|
||||
: AppLoader(directory->GetFile("main")), dir(std::move(directory)),
|
||||
override_update(override_update) {}
|
||||
|
||||
FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) {
|
||||
@@ -89,7 +100,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
dir = file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
const FileSys::VirtualFile npdm = dir->GetFile("main.npdm");
|
||||
// Read meta to determine title ID
|
||||
FileSys::VirtualFile npdm = dir->GetFile("main.npdm");
|
||||
if (npdm == nullptr)
|
||||
return ResultStatus::ErrorMissingNPDM;
|
||||
|
||||
@@ -97,6 +109,21 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
if (result != ResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (override_update) {
|
||||
const FileSys::PatchManager patch_manager(metadata.GetTitleID());
|
||||
dir = patch_manager.PatchExeFS(dir);
|
||||
}
|
||||
|
||||
// Reread in case PatchExeFS affected the main.npdm
|
||||
npdm = dir->GetFile("main.npdm");
|
||||
if (npdm == nullptr)
|
||||
return ResultStatus::ErrorMissingNPDM;
|
||||
|
||||
ResultStatus result2 = metadata.Load(npdm);
|
||||
if (result2 != ResultStatus::Success) {
|
||||
return result2;
|
||||
}
|
||||
metadata.Print();
|
||||
|
||||
const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()};
|
||||
@@ -119,7 +146,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
title_id = metadata.GetTitleID();
|
||||
process->program_id = metadata.GetTitleID();
|
||||
process->svc_access_mask.set();
|
||||
process->resource_limit =
|
||||
@@ -170,4 +196,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadTitle(std::string& title)
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -20,10 +20,12 @@ namespace Loader {
|
||||
*/
|
||||
class AppLoader_DeconstructedRomDirectory final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file);
|
||||
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file,
|
||||
bool override_update = false);
|
||||
|
||||
// Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
|
||||
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory);
|
||||
explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
|
||||
bool override_update = false);
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
@@ -42,6 +44,7 @@ public:
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
bool IsRomFSUpdatable() const override;
|
||||
|
||||
private:
|
||||
FileSys::ProgramMetadata metadata;
|
||||
@@ -51,6 +54,7 @@ private:
|
||||
std::vector<u8> icon_data;
|
||||
std::string name;
|
||||
u64 title_id{};
|
||||
bool override_update;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 50> RESULT_MESSAGES{
|
||||
constexpr std::array<const char*, 58> RESULT_MESSAGES{
|
||||
"The operation completed successfully.",
|
||||
"The loader requested to load is already loaded.",
|
||||
"The operation is not implemented.",
|
||||
@@ -143,7 +143,16 @@ constexpr std::array<const char*, 50> RESULT_MESSAGES{
|
||||
"The AES Key Generation Source could not be found.",
|
||||
"The SD Save Key Source could not be found.",
|
||||
"The SD NCA Key Source could not be found.",
|
||||
"The NSP file is missing a Program-type NCA."};
|
||||
"The NSP file is missing a Program-type NCA.",
|
||||
"The BKTR-type NCA has a bad BKTR header.",
|
||||
"The BKTR Subsection entry is not located immediately after the Relocation entry.",
|
||||
"The BKTR Subsection entry is not at the end of the media block.",
|
||||
"The BKTR-type NCA has a bad Relocation block.",
|
||||
"The BKTR-type NCA has a bad Subsection block.",
|
||||
"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.",
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
||||
os << RESULT_MESSAGES.at(static_cast<size_t>(status));
|
||||
|
||||
@@ -107,6 +107,14 @@ enum class ResultStatus : u16 {
|
||||
ErrorMissingSDSaveKeySource,
|
||||
ErrorMissingSDNCAKeySource,
|
||||
ErrorNSPMissingProgramNCA,
|
||||
ErrorBadBKTRHeader,
|
||||
ErrorBKTRSubsectionNotAfterRelocation,
|
||||
ErrorBKTRSubsectionNotAtEnd,
|
||||
ErrorBadRelocationBlock,
|
||||
ErrorBadSubsectionBlock,
|
||||
ErrorBadRelocationBuckets,
|
||||
ErrorBadSubsectionBuckets,
|
||||
ErrorMissingBKTRBaseRomFS,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
||||
@@ -197,13 +205,22 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the update RomFS of the application
|
||||
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
|
||||
* @param file The file containing the RomFS
|
||||
* @return ResultStatus result of function
|
||||
* Get whether or not updates can be applied to the RomFS.
|
||||
* By default, this is true, however for formats where it cannot be guaranteed that the RomFS is
|
||||
* the base game it should be set to false.
|
||||
* @return bool whether or not updatable.
|
||||
*/
|
||||
virtual ResultStatus ReadUpdateRomFS(FileSys::VirtualFile& file) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
virtual bool IsRomFSUpdatable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the difference between the start of the IVFC header and the start of level 6 (RomFS)
|
||||
* data. Needed for bktr patching.
|
||||
* @return IVFC offset for romfs.
|
||||
*/
|
||||
virtual u64 ReadRomFSIVFCOffset() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,7 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (exefs == nullptr)
|
||||
return ResultStatus::ErrorNoExeFS;
|
||||
|
||||
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs);
|
||||
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
|
||||
|
||||
const auto load_result = directory_loader->Load(process);
|
||||
if (load_result != ResultStatus::Success)
|
||||
@@ -71,6 +71,12 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
|
||||
if (nca == nullptr)
|
||||
return 0;
|
||||
return nca->GetBaseIVFCOffset();
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
|
||||
if (nca == nullptr || nca->GetStatus() != ResultStatus::Success)
|
||||
return ResultStatus::ErrorNotInitialized;
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
|
||||
u64 ReadRomFSIVFCOffset() const override;
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -232,4 +232,9 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) {
|
||||
title = nacp->GetApplicationName();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
bool AppLoader_NRO::IsRomFSUpdatable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
bool IsRomFSUpdatable() const override;
|
||||
|
||||
private:
|
||||
bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#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/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -28,24 +30,12 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
|
||||
return;
|
||||
|
||||
const auto control_nca =
|
||||
nsp->GetNCA(nsp->GetFirstTitleID(), FileSys::ContentRecordType::Control);
|
||||
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
|
||||
if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
|
||||
return;
|
||||
|
||||
const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS());
|
||||
if (romfs == nullptr)
|
||||
return;
|
||||
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr)
|
||||
break;
|
||||
}
|
||||
|
||||
const auto nacp_raw = romfs->GetFile("control.nacp");
|
||||
if (nacp_raw == nullptr)
|
||||
return;
|
||||
nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca);
|
||||
}
|
||||
|
||||
AppLoader_NSP::~AppLoader_NSP() = default;
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/nca.h"
|
||||
#include "core/loader/xci.h"
|
||||
@@ -20,21 +22,13 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
|
||||
nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
|
||||
if (xci->GetStatus() != ResultStatus::Success)
|
||||
return;
|
||||
|
||||
const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
|
||||
if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
|
||||
return;
|
||||
const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS());
|
||||
if (romfs == nullptr)
|
||||
return;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr)
|
||||
break;
|
||||
}
|
||||
const auto nacp_raw = romfs->GetFile("control.nacp");
|
||||
if (nacp_raw == nullptr)
|
||||
return;
|
||||
nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
|
||||
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca);
|
||||
}
|
||||
|
||||
AppLoader_XCI::~AppLoader_XCI() = default;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "common/file_util.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
@@ -88,12 +90,28 @@ TelemetrySession::TelemetrySession() {
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count()};
|
||||
AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
|
||||
std::string program_name;
|
||||
const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadTitle(program_name)};
|
||||
|
||||
u64 program_id{};
|
||||
const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
|
||||
if (res == Loader::ResultStatus::Success) {
|
||||
AddField(Telemetry::FieldType::Session, "ProgramName", program_name);
|
||||
AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
|
||||
|
||||
std::string name;
|
||||
System::GetInstance().GetAppLoader().ReadTitle(name);
|
||||
|
||||
if (name.empty()) {
|
||||
auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
|
||||
if (nacp != nullptr)
|
||||
name = nacp->GetApplicationName();
|
||||
}
|
||||
|
||||
if (!name.empty())
|
||||
AddField(Telemetry::FieldType::Session, "ProgramName", name);
|
||||
}
|
||||
|
||||
AddField(Telemetry::FieldType::Session, "ProgramFormat",
|
||||
static_cast<u8>(System::GetInstance().GetAppLoader().GetFileType()));
|
||||
|
||||
// Log application information
|
||||
Telemetry::AppendBuildInfo(field_collection);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ add_library(video_core STATIC
|
||||
renderer_base.cpp
|
||||
renderer_base.h
|
||||
renderer_opengl/gl_buffer_cache.cpp
|
||||
renderer_opengl/gl_buffer_cache.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
|
||||
@@ -293,10 +293,6 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
|
||||
"TIC versions other than BlockLinear or Pitch are unimplemented");
|
||||
|
||||
ASSERT_MSG((tic_entry.texture_type == Texture::TextureType::Texture2D) ||
|
||||
(tic_entry.texture_type == Texture::TextureType::Texture2DNoMipmap),
|
||||
"Texture types other than Texture2D are unimplemented");
|
||||
|
||||
auto r_type = tic_entry.r_type.Value();
|
||||
auto g_type = tic_entry.g_type.Value();
|
||||
auto b_type = tic_entry.b_type.Value();
|
||||
|
||||
@@ -127,6 +127,7 @@ public:
|
||||
BitField<21, 6, Size> size;
|
||||
BitField<27, 3, Type> type;
|
||||
BitField<31, 1, u32> bgra;
|
||||
u32 hex;
|
||||
};
|
||||
|
||||
u32 ComponentCount() const {
|
||||
@@ -262,6 +263,10 @@ public:
|
||||
bool IsValid() const {
|
||||
return size != Size::Invalid;
|
||||
}
|
||||
|
||||
bool operator<(const VertexAttribute& other) const {
|
||||
return hex < other.hex;
|
||||
}
|
||||
};
|
||||
|
||||
enum class PrimitiveTopology : u32 {
|
||||
@@ -545,7 +550,7 @@ public:
|
||||
|
||||
INSERT_PADDING_WORDS(0x5B);
|
||||
|
||||
VertexAttribute vertex_attrib_format[NumVertexAttributes];
|
||||
std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format;
|
||||
|
||||
INSERT_PADDING_WORDS(0xF);
|
||||
|
||||
@@ -964,7 +969,7 @@ ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
|
||||
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
|
||||
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
|
||||
ASSERT_REG_POSITION(zeta, 0x3F8);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
|
||||
ASSERT_REG_POSITION(rt_control, 0x487);
|
||||
ASSERT_REG_POSITION(zeta_width, 0x48a);
|
||||
ASSERT_REG_POSITION(zeta_height, 0x48b);
|
||||
|
||||
@@ -76,6 +76,7 @@ union Attribute {
|
||||
Position = 7,
|
||||
Attribute_0 = 8,
|
||||
Attribute_31 = 39,
|
||||
PointCoord = 46,
|
||||
// This attribute contains a tuple of (~, ~, InstanceId, VertexId) when inside a vertex
|
||||
// shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval
|
||||
// shader.
|
||||
@@ -246,6 +247,17 @@ enum class TextureType : u64 {
|
||||
enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 };
|
||||
enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 };
|
||||
|
||||
struct IpaMode {
|
||||
IpaInterpMode interpolation_mode;
|
||||
IpaSampleMode sampling_mode;
|
||||
inline bool operator==(const IpaMode& a) {
|
||||
return (a.interpolation_mode == interpolation_mode) && (a.sampling_mode == sampling_mode);
|
||||
}
|
||||
inline bool operator!=(const IpaMode& a) {
|
||||
return !((*this) == a);
|
||||
}
|
||||
};
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
@@ -75,7 +77,7 @@ void OGLBufferCache::Unmap() {
|
||||
stream_buffer.Unmap(buffer_offset - buffer_offset_base);
|
||||
}
|
||||
|
||||
GLuint OGLBufferCache::GetHandle() {
|
||||
GLuint OGLBufferCache::GetHandle() const {
|
||||
return stream_buffer.GetHandle();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
@@ -31,7 +31,7 @@ struct CachedBufferEntry final {
|
||||
|
||||
class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
|
||||
public:
|
||||
OGLBufferCache(size_t size);
|
||||
explicit OGLBufferCache(size_t size);
|
||||
|
||||
GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, size_t size, size_t alignment = 4,
|
||||
bool cache = true);
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
void Map(size_t max_size);
|
||||
void Unmap();
|
||||
|
||||
GLuint GetHandle();
|
||||
GLuint GetHandle() const;
|
||||
|
||||
protected:
|
||||
void AlignBuffer(size_t alignment);
|
||||
@@ -49,9 +49,9 @@ protected:
|
||||
private:
|
||||
OGLStreamBuffer stream_buffer;
|
||||
|
||||
u8* buffer_ptr;
|
||||
GLintptr buffer_offset;
|
||||
GLintptr buffer_offset_base;
|
||||
u8* buffer_ptr = nullptr;
|
||||
GLintptr buffer_offset = 0;
|
||||
GLintptr buffer_offset_base = 0;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -70,28 +70,13 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
|
||||
// Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
|
||||
state.clip_distance[0] = true;
|
||||
|
||||
// Generate VAO and UBO
|
||||
sw_vao.Create();
|
||||
uniform_buffer.Create();
|
||||
|
||||
state.draw.vertex_array = sw_vao.handle;
|
||||
state.draw.uniform_buffer = uniform_buffer.handle;
|
||||
state.Apply();
|
||||
|
||||
// Create render framebuffer
|
||||
framebuffer.Create();
|
||||
|
||||
hw_vao.Create();
|
||||
|
||||
state.draw.vertex_buffer = buffer_cache.GetHandle();
|
||||
|
||||
shader_program_manager = std::make_unique<GLShader::ProgramManager>();
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.vertex_array = hw_vao.handle;
|
||||
state.Apply();
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_cache.GetHandle());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
|
||||
@@ -106,7 +91,54 @@ void RasterizerOpenGL::SetupVertexArrays() {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
state.draw.vertex_array = hw_vao.handle;
|
||||
auto [iter, is_cache_miss] = vertex_array_cache.try_emplace(regs.vertex_attrib_format);
|
||||
auto& VAO = iter->second;
|
||||
|
||||
if (is_cache_miss) {
|
||||
VAO.Create();
|
||||
state.draw.vertex_array = VAO.handle;
|
||||
state.Apply();
|
||||
|
||||
// The index buffer binding is stored within the VAO. Stupid OpenGL, but easy to work
|
||||
// around.
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_cache.GetHandle());
|
||||
|
||||
// Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
|
||||
// Enables the first 16 vertex attributes always, as we don't know which ones are actually
|
||||
// used until shader time. Note, Tegra technically supports 32, but we're capping this to 16
|
||||
// for now to avoid OpenGL errors.
|
||||
// TODO(Subv): Analyze the shader to identify which attributes are actually used and don't
|
||||
// assume every shader uses them all.
|
||||
for (unsigned index = 0; index < 16; ++index) {
|
||||
const auto& attrib = regs.vertex_attrib_format[index];
|
||||
|
||||
// Ignore invalid attributes.
|
||||
if (!attrib.IsValid())
|
||||
continue;
|
||||
|
||||
const auto& buffer = regs.vertex_array[attrib.buffer];
|
||||
LOG_TRACE(HW_GPU,
|
||||
"vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
|
||||
index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(),
|
||||
attrib.offset.Value(), attrib.IsNormalized());
|
||||
|
||||
ASSERT(buffer.IsEnabled());
|
||||
|
||||
glEnableVertexAttribArray(index);
|
||||
if (attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::SignedInt ||
|
||||
attrib.type ==
|
||||
Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::UnsignedInt) {
|
||||
glVertexAttribIFormat(index, attrib.ComponentCount(),
|
||||
MaxwellToGL::VertexType(attrib), attrib.offset);
|
||||
} else {
|
||||
glVertexAttribFormat(index, attrib.ComponentCount(),
|
||||
MaxwellToGL::VertexType(attrib),
|
||||
attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset);
|
||||
}
|
||||
glVertexAttribBinding(index, attrib.buffer);
|
||||
}
|
||||
}
|
||||
state.draw.vertex_array = VAO.handle;
|
||||
state.draw.vertex_buffer = buffer_cache.GetHandle();
|
||||
state.Apply();
|
||||
|
||||
@@ -119,66 +151,27 @@ void RasterizerOpenGL::SetupVertexArrays() {
|
||||
Tegra::GPUVAddr start = vertex_array.StartAddress();
|
||||
const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
|
||||
|
||||
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
|
||||
start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor);
|
||||
}
|
||||
|
||||
ASSERT(end > start);
|
||||
u64 size = end - start + 1;
|
||||
|
||||
GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
|
||||
const u64 size = end - start + 1;
|
||||
const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
|
||||
|
||||
// Bind the vertex array to the buffer at the current offset.
|
||||
glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset,
|
||||
vertex_array.stride);
|
||||
|
||||
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
|
||||
// Tell OpenGL that this is an instanced vertex buffer to prevent accessing different
|
||||
// indexes on each vertex. We do the instance indexing manually by incrementing the
|
||||
// start address of the vertex buffer.
|
||||
glVertexBindingDivisor(index, 1);
|
||||
// Enable vertex buffer instancing with the specified divisor.
|
||||
glVertexBindingDivisor(index, vertex_array.divisor);
|
||||
} else {
|
||||
// Disable the vertex buffer instancing.
|
||||
glVertexBindingDivisor(index, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
|
||||
// Enables the first 16 vertex attributes always, as we don't know which ones are actually used
|
||||
// until shader time. Note, Tegra technically supports 32, but we're capping this to 16 for now
|
||||
// to avoid OpenGL errors.
|
||||
// TODO(Subv): Analyze the shader to identify which attributes are actually used and don't
|
||||
// assume every shader uses them all.
|
||||
for (unsigned index = 0; index < 16; ++index) {
|
||||
auto& attrib = regs.vertex_attrib_format[index];
|
||||
|
||||
// Ignore invalid attributes.
|
||||
if (!attrib.IsValid())
|
||||
continue;
|
||||
|
||||
auto& buffer = regs.vertex_array[attrib.buffer];
|
||||
LOG_TRACE(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}",
|
||||
index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(),
|
||||
attrib.offset.Value(), attrib.IsNormalized());
|
||||
|
||||
ASSERT(buffer.IsEnabled());
|
||||
|
||||
glEnableVertexAttribArray(index);
|
||||
if (attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::SignedInt ||
|
||||
attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::UnsignedInt) {
|
||||
glVertexAttribIFormat(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib),
|
||||
attrib.offset);
|
||||
} else {
|
||||
glVertexAttribFormat(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib),
|
||||
attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset);
|
||||
}
|
||||
glVertexAttribBinding(index, attrib.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupShaders() {
|
||||
MICROPROFILE_SCOPE(OpenGL_Shader);
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
|
||||
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
|
||||
@@ -186,7 +179,7 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
u32 current_texture_bindpoint = 0;
|
||||
|
||||
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
auto& shader_config = gpu.regs.shader_config[index];
|
||||
const auto& shader_config = gpu.regs.shader_config[index];
|
||||
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
|
||||
// Skip stages that are not enabled
|
||||
@@ -198,7 +191,7 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
|
||||
GLShader::MaxwellUniformData ubo{};
|
||||
ubo.SetFromRegs(gpu.state.shader_stages[stage]);
|
||||
GLintptr offset = buffer_cache.UploadHostMemory(
|
||||
const GLintptr offset = buffer_cache.UploadHostMemory(
|
||||
&ubo, sizeof(ubo), static_cast<size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Bind the buffer
|
||||
@@ -237,6 +230,8 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
}
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
shader_program_manager->UseTrivialGeometryShader();
|
||||
}
|
||||
|
||||
@@ -430,11 +425,12 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
ScopeAcquireGLContext acquire_context{emu_window};
|
||||
|
||||
auto [dirty_color_surface, dirty_depth_surface] =
|
||||
const auto [dirty_color_surface, dirty_depth_surface] =
|
||||
ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true);
|
||||
|
||||
SyncDepthTestState();
|
||||
@@ -448,7 +444,8 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
// Draw the vertex batch
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
|
||||
const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
|
||||
static_cast<u64>(regs.index_array.FormatSizeInBytes())};
|
||||
|
||||
state.draw.vertex_buffer = buffer_cache.GetHandle();
|
||||
state.Apply();
|
||||
@@ -491,13 +488,29 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
|
||||
|
||||
// Adjust the index buffer offset so it points to the first desired index.
|
||||
index_buffer_offset += regs.index_array.first * regs.index_array.FormatSizeInBytes();
|
||||
index_buffer_offset += static_cast<GLintptr>(regs.index_array.first) *
|
||||
static_cast<GLintptr>(regs.index_array.FormatSizeInBytes());
|
||||
|
||||
glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset), base_vertex);
|
||||
if (gpu.state.current_instance > 0) {
|
||||
glDrawElementsInstancedBaseVertexBaseInstance(
|
||||
primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
|
||||
gpu.state.current_instance);
|
||||
} else {
|
||||
glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset),
|
||||
base_vertex);
|
||||
}
|
||||
} else {
|
||||
glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
|
||||
if (gpu.state.current_instance > 0) {
|
||||
glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
|
||||
regs.vertex_buffer.count, 1,
|
||||
gpu.state.current_instance);
|
||||
} else {
|
||||
glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable scissor test
|
||||
@@ -514,13 +527,9 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {}
|
||||
|
||||
void RasterizerOpenGL::FlushAll() {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
}
|
||||
void RasterizerOpenGL::FlushAll() {}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
}
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
@@ -530,7 +539,6 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
@@ -578,7 +586,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
void RasterizerOpenGL::SamplerInfo::Create() {
|
||||
sampler.Create();
|
||||
mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
|
||||
wrap_u = wrap_v = Tegra::Texture::WrapMode::Wrap;
|
||||
wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
|
||||
|
||||
// default is GL_LINEAR_MIPMAP_LINEAR
|
||||
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
@@ -586,7 +594,7 @@ void RasterizerOpenGL::SamplerInfo::Create() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
|
||||
GLuint s = sampler.handle;
|
||||
const GLuint s = sampler.handle;
|
||||
|
||||
if (mag_filter != config.mag_filter) {
|
||||
mag_filter = config.mag_filter;
|
||||
@@ -605,8 +613,13 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
|
||||
wrap_v = config.wrap_v;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v));
|
||||
}
|
||||
if (wrap_p != config.wrap_p) {
|
||||
wrap_p = config.wrap_p;
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
|
||||
}
|
||||
|
||||
if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border) {
|
||||
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,
|
||||
config.border_color_b, config.border_color_a}};
|
||||
if (border_color != new_border_color) {
|
||||
@@ -666,8 +679,6 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
|
||||
current_bindpoint + bindpoint);
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
return current_bindpoint + static_cast<u32>(entries.size());
|
||||
}
|
||||
|
||||
@@ -682,7 +693,7 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& entry = entries[bindpoint];
|
||||
u32 current_bindpoint = current_unit + bindpoint;
|
||||
const u32 current_bindpoint = current_unit + bindpoint;
|
||||
|
||||
// Bind the uniform to the sampler.
|
||||
|
||||
@@ -692,14 +703,15 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
|
||||
|
||||
if (!texture.enabled) {
|
||||
state.texture_units[current_bindpoint].texture_2d = 0;
|
||||
state.texture_units[current_bindpoint].texture = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc);
|
||||
Surface surface = res_cache.GetTextureSurface(texture);
|
||||
if (surface != nullptr) {
|
||||
state.texture_units[current_bindpoint].texture_2d = surface->Texture().handle;
|
||||
state.texture_units[current_bindpoint].texture = surface->Texture().handle;
|
||||
state.texture_units[current_bindpoint].target = surface->Target();
|
||||
state.texture_units[current_bindpoint].swizzle.r =
|
||||
MaxwellToGL::SwizzleSource(texture.tic.x_source);
|
||||
state.texture_units[current_bindpoint].swizzle.g =
|
||||
@@ -710,12 +722,10 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
MaxwellToGL::SwizzleSource(texture.tic.w_source);
|
||||
} else {
|
||||
// Can occur when texture addr is null or its memory is unmapped/invalid
|
||||
state.texture_units[current_bindpoint].texture_2d = 0;
|
||||
state.texture_units[current_bindpoint].texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
return current_unit + static_cast<u32>(entries.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
@@ -92,6 +93,7 @@ private:
|
||||
Tegra::Texture::TextureFilter min_filter;
|
||||
Tegra::Texture::WrapMode wrap_u;
|
||||
Tegra::Texture::WrapMode wrap_v;
|
||||
Tegra::Texture::WrapMode wrap_p;
|
||||
GLvec4 border_color;
|
||||
};
|
||||
|
||||
@@ -168,14 +170,15 @@ private:
|
||||
ScreenInfo& screen_info;
|
||||
|
||||
std::unique_ptr<GLShader::ProgramManager> shader_program_manager;
|
||||
OGLVertexArray sw_vao;
|
||||
OGLVertexArray hw_vao;
|
||||
std::map<std::array<Tegra::Engines::Maxwell3D::Regs::VertexAttribute,
|
||||
Tegra::Engines::Maxwell3D::Regs::NumVertexAttributes>,
|
||||
OGLVertexArray>
|
||||
vertex_array_cache;
|
||||
|
||||
std::array<SamplerInfo, GLShader::NumTextureSamplers> texture_samplers;
|
||||
|
||||
static constexpr size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
|
||||
OGLBufferCache buffer_cache;
|
||||
OGLBuffer uniform_buffer;
|
||||
OGLFramebuffer framebuffer;
|
||||
GLint uniform_buffer_alignment;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
@@ -51,10 +52,12 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
|
||||
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
|
||||
params.depth = config.tic.Depth();
|
||||
params.unaligned_height = config.tic.Height();
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.cache_width = Common::AlignUp(params.width, 16);
|
||||
params.cache_height = Common::AlignUp(params.height, 16);
|
||||
params.cache_width = Common::AlignUp(params.width, 8);
|
||||
params.cache_height = Common::AlignUp(params.height, 8);
|
||||
params.target = SurfaceTargetFromTextureType(config.tic.texture_type);
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -69,10 +72,12 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = config.width;
|
||||
params.height = config.height;
|
||||
params.depth = 1;
|
||||
params.unaligned_height = config.height;
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.cache_width = Common::AlignUp(params.width, 16);
|
||||
params.cache_height = Common::AlignUp(params.height, 16);
|
||||
params.cache_width = Common::AlignUp(params.width, 8);
|
||||
params.cache_height = Common::AlignUp(params.height, 8);
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -86,13 +91,14 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
params.component_type = ComponentTypeFromDepthFormat(format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.width = zeta_width;
|
||||
params.height = zeta_height;
|
||||
params.depth = 1;
|
||||
params.unaligned_height = zeta_height;
|
||||
params.size_in_bytes = params.SizeInBytes();
|
||||
params.cache_width = Common::AlignUp(params.width, 16);
|
||||
params.cache_height = Common::AlignUp(params.height, 16);
|
||||
params.cache_width = Common::AlignUp(params.width, 8);
|
||||
params.cache_height = Common::AlignUp(params.height, 8);
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -166,6 +172,26 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
ComponentType::Float, false}, // Z32FS8
|
||||
}};
|
||||
|
||||
static GLenum SurfaceTargetToGL(SurfaceParams::SurfaceTarget target) {
|
||||
switch (target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
return GL_TEXTURE_1D;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
return GL_TEXTURE_2D;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
return GL_TEXTURE_3D;
|
||||
case SurfaceParams::SurfaceTarget::Texture1DArray:
|
||||
return GL_TEXTURE_1D_ARRAY;
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
return GL_TEXTURE_2D_ARRAY;
|
||||
case SurfaceParams::SurfaceTarget::TextureCubemap:
|
||||
return GL_TEXTURE_CUBE_MAP;
|
||||
}
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) {
|
||||
ASSERT(static_cast<size_t>(pixel_format) < tex_format_tuples.size());
|
||||
auto& format = tex_format_tuples[static_cast<unsigned int>(pixel_format)];
|
||||
@@ -220,7 +246,8 @@ static bool IsFormatBCn(PixelFormat format) {
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer, VAddr addr) {
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, size_t gl_buffer_size,
|
||||
VAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
|
||||
|
||||
@@ -230,18 +257,18 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_bu
|
||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
||||
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
|
||||
addr, tile_size, bytes_per_pixel, stride, height, block_height);
|
||||
const size_t size_to_copy{std::min(gl_buffer.size(), data.size())};
|
||||
gl_buffer.assign(data.begin(), data.begin() + size_to_copy);
|
||||
const size_t size_to_copy{std::min(gl_buffer_size, data.size())};
|
||||
memcpy(gl_buffer, data.data(), size_to_copy);
|
||||
} else {
|
||||
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
|
||||
// check the configuration for this and perform more generic un/swizzle
|
||||
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
|
||||
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
|
||||
Memory::GetPointer(addr), gl_buffer.data(), morton_to_gl);
|
||||
Memory::GetPointer(addr), gl_buffer, morton_to_gl);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, size_t, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
morton_to_gl_fns = {
|
||||
// clang-format off
|
||||
@@ -298,7 +325,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, size_t, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
gl_to_morton_fns = {
|
||||
// clang-format off
|
||||
@@ -357,33 +384,6 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
// Allocate an uninitialized texture of appropriate size and format for the surface
|
||||
static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width,
|
||||
u32 height) {
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
// Keep track of previous texture bindings
|
||||
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
||||
cur_state.texture_units[0].texture_2d = texture;
|
||||
cur_state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (!format_tuple.compressed) {
|
||||
// Only pre-create the texture for non-compressed textures.
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0,
|
||||
format_tuple.format, format_tuple.type, nullptr);
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
// Restore previous texture bindings
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex,
|
||||
const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type,
|
||||
GLuint read_fb_handle, GLuint draw_fb_handle) {
|
||||
@@ -438,12 +438,56 @@ static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rec
|
||||
return true;
|
||||
}
|
||||
|
||||
CachedSurface::CachedSurface(const SurfaceParams& params) : params(params) {
|
||||
CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
: params(params), gl_target(SurfaceTargetToGL(params.target)) {
|
||||
texture.Create();
|
||||
const auto& rect{params.GetRect()};
|
||||
AllocateSurfaceTexture(texture.handle,
|
||||
GetFormatTuple(params.pixel_format, params.component_type),
|
||||
rect.GetWidth(), rect.GetHeight());
|
||||
|
||||
// Keep track of previous texture bindings
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
const auto& old_tex = cur_state.texture_units[0];
|
||||
SCOPE_EXIT({
|
||||
cur_state.texture_units[0] = old_tex;
|
||||
cur_state.Apply();
|
||||
});
|
||||
|
||||
cur_state.texture_units[0].texture = texture.handle;
|
||||
cur_state.texture_units[0].target = SurfaceTargetToGL(params.target);
|
||||
cur_state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
if (!format_tuple.compressed) {
|
||||
// Only pre-create the texture for non-compressed textures.
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
glTexImage1D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format,
|
||||
rect.GetWidth(), 0, format_tuple.format, format_tuple.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glTexImage2D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format,
|
||||
rect.GetWidth(), rect.GetHeight(), 0, format_tuple.format,
|
||||
format_tuple.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glTexImage3D(SurfaceTargetToGL(params.target), 0, format_tuple.internal_format,
|
||||
rect.GetWidth(), rect.GetHeight(), params.depth, 0, format_tuple.format,
|
||||
format_tuple.type, nullptr);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, rect.GetWidth(),
|
||||
rect.GetHeight(), 0, format_tuple.format, format_tuple.type, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
@@ -514,23 +558,6 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform software conversion (as needed) when flushing a buffer to Switch
|
||||
* memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or with
|
||||
* typical desktop GPUs.
|
||||
*/
|
||||
static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& /*data*/, PixelFormat pixel_format,
|
||||
u32 /*width*/, u32 /*height*/) {
|
||||
switch (pixel_format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::S8Z24:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented pixel_format={}",
|
||||
static_cast<u32>(pixel_format));
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
|
||||
void CachedSurface::LoadGLBuffer() {
|
||||
ASSERT(params.type != SurfaceType::Fill);
|
||||
@@ -545,13 +572,24 @@ void CachedSurface::LoadGLBuffer() {
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
|
||||
|
||||
if (params.is_tiled) {
|
||||
gl_buffer.resize(copy_size);
|
||||
// TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do
|
||||
// this for 3D textures, etc.
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
// Pass impl. to the fallback code below
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
gl_buffer.resize(params.depth * copy_size);
|
||||
morton_to_gl_fns[static_cast<size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, gl_buffer, params.addr);
|
||||
params.width, params.block_height, params.height, gl_buffer.data(), copy_size,
|
||||
params.addr);
|
||||
} else {
|
||||
const u8* const texture_src_data_end = texture_src_data + copy_size;
|
||||
|
||||
const u8* const texture_src_data_end{texture_src_data + (params.depth * copy_size)};
|
||||
gl_buffer.assign(texture_src_data, texture_src_data_end);
|
||||
}
|
||||
|
||||
@@ -560,23 +598,7 @@ void CachedSurface::LoadGLBuffer() {
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::FlushGLBuffer() {
|
||||
u8* const dst_buffer = Memory::GetPointer(params.addr);
|
||||
|
||||
ASSERT(dst_buffer);
|
||||
ASSERT(gl_buffer.size() ==
|
||||
params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
|
||||
|
||||
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
|
||||
params.height);
|
||||
|
||||
if (!params.is_tiled) {
|
||||
std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes);
|
||||
} else {
|
||||
gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, gl_buffer, params.addr);
|
||||
}
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
|
||||
@@ -587,7 +609,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureUL);
|
||||
|
||||
ASSERT(gl_buffer.size() ==
|
||||
params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
|
||||
params.width * params.height * GetGLBytesPerPixel(params.pixel_format) * params.depth);
|
||||
|
||||
const auto& rect{params.GetRect()};
|
||||
|
||||
@@ -600,8 +622,13 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
GLuint target_tex = texture.handle;
|
||||
OpenGLState cur_state = OpenGLState::GetCurState();
|
||||
|
||||
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
||||
cur_state.texture_units[0].texture_2d = target_tex;
|
||||
const auto& old_tex = cur_state.texture_units[0];
|
||||
SCOPE_EXIT({
|
||||
cur_state.texture_units[0] = old_tex;
|
||||
cur_state.Apply();
|
||||
});
|
||||
cur_state.texture_units[0].texture = target_tex;
|
||||
cur_state.texture_units[0].target = SurfaceTargetToGL(params.target);
|
||||
cur_state.Apply();
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
@@ -610,74 +637,68 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
if (tuple.compressed) {
|
||||
glCompressedTexImage2D(
|
||||
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
|
||||
static_cast<GLsizei>(params.height), 0, static_cast<GLsizei>(params.size_in_bytes),
|
||||
&gl_buffer[buffer_offset]);
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glCompressedTexImage2D(
|
||||
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
|
||||
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
|
||||
static_cast<GLsizei>(params.size_in_bytes), &gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glCompressedTexImage3D(
|
||||
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
|
||||
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
|
||||
static_cast<GLsizei>(params.depth), 0, static_cast<GLsizei>(params.size_in_bytes),
|
||||
&gl_buffer[buffer_offset]);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
glCompressedTexImage2D(
|
||||
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
|
||||
static_cast<GLsizei>(params.height), 0, static_cast<GLsizei>(params.size_in_bytes),
|
||||
&gl_buffer[buffer_offset]);
|
||||
}
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
glTexSubImage1D(SurfaceTargetToGL(params.target), 0, x0,
|
||||
static_cast<GLsizei>(rect.GetWidth()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glTexSubImage2D(SurfaceTargetToGL(params.target), 0, x0, y0,
|
||||
static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glTexSubImage3D(SurfaceTargetToGL(params.target), 0, x0, y0, 0,
|
||||
static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format,
|
||||
tuple.type, &gl_buffer[buffer_offset]);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
}
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::DownloadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
|
||||
if (params.type == SurfaceType::Fill)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureDL);
|
||||
|
||||
gl_buffer.resize(params.width * params.height * GetGLBytesPerPixel(params.pixel_format));
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
OpenGLState prev_state = state;
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
|
||||
// Ensure no bad interactions with GL_PACK_ALIGNMENT
|
||||
ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
|
||||
|
||||
const auto& rect{params.GetRect()};
|
||||
size_t buffer_offset =
|
||||
(rect.bottom * params.width + rect.left) * GetGLBytesPerPixel(params.pixel_format);
|
||||
|
||||
state.UnbindTexture(texture.handle);
|
||||
state.draw.read_framebuffer = read_fb_handle;
|
||||
state.Apply();
|
||||
|
||||
if (params.type == SurfaceType::ColorTexture) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
texture.handle, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
} else if (params.type == SurfaceType::Depth) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, 0);
|
||||
}
|
||||
glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom),
|
||||
static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()),
|
||||
tuple.format, tuple.type, &gl_buffer[buffer_offset]);
|
||||
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
|
||||
read_framebuffer.Create();
|
||||
draw_framebuffer.Create();
|
||||
copy_pbo.Create();
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config) {
|
||||
@@ -748,7 +769,6 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
|
||||
surface->DownloadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
|
||||
surface->FlushGLBuffer();
|
||||
}
|
||||
|
||||
@@ -809,8 +829,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
// If format is unchanged, we can do a faster blit without reinterpreting pixel data
|
||||
if (params.pixel_format == new_params.pixel_format) {
|
||||
BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle,
|
||||
new_surface->GetSurfaceParams().GetRect(), params.type,
|
||||
read_framebuffer.handle, draw_framebuffer.handle);
|
||||
params.GetRect(), params.type, read_framebuffer.handle,
|
||||
draw_framebuffer.handle);
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
@@ -821,12 +841,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
|
||||
size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes());
|
||||
|
||||
// Use a Pixel Buffer Object to download the previous texture and then upload it to the new
|
||||
// one using the new format.
|
||||
OGLBuffer pbo;
|
||||
pbo.Create();
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
|
||||
if (source_format.compressed) {
|
||||
glGetCompressedTextureImage(surface->Texture().handle, 0,
|
||||
@@ -845,8 +860,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
// of the data in this case. Games like Super Mario Odyssey seem to hit this case
|
||||
// when drawing, it re-uses the memory of a previous texture as a bigger framebuffer
|
||||
// but it doesn't clear it beforehand, the texture is already full of zeros.
|
||||
LOG_CRITICAL(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
|
||||
std::vector<u8> data(remaining_size);
|
||||
@@ -859,21 +874,38 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
|
||||
const auto& dest_rect{new_params.GetRect()};
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo.handle);
|
||||
if (dest_format.compressed) {
|
||||
glCompressedTexSubImage2D(
|
||||
GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
|
||||
LOG_CRITICAL(HW_GPU, "Compressed copy is unimplemented!");
|
||||
UNREACHABLE();
|
||||
} else {
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
switch (new_params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture1D:
|
||||
glTextureSubImage1D(new_surface->Texture().handle, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
glTextureSubImage3D(new_surface->Texture().handle, 0, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()),
|
||||
static_cast<GLsizei>(new_params.depth), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
pbo.Release();
|
||||
}
|
||||
|
||||
return new_surface;
|
||||
|
||||
@@ -109,6 +109,33 @@ struct SurfaceParams {
|
||||
Invalid = 4,
|
||||
};
|
||||
|
||||
enum class SurfaceTarget {
|
||||
Texture1D,
|
||||
Texture2D,
|
||||
Texture3D,
|
||||
Texture1DArray,
|
||||
Texture2DArray,
|
||||
TextureCubemap,
|
||||
};
|
||||
|
||||
static SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type) {
|
||||
switch (texture_type) {
|
||||
case Tegra::Texture::TextureType::Texture1D:
|
||||
return SurfaceTarget::Texture1D;
|
||||
case Tegra::Texture::TextureType::Texture2D:
|
||||
case Tegra::Texture::TextureType::Texture2DNoMipmap:
|
||||
return SurfaceTarget::Texture2D;
|
||||
case Tegra::Texture::TextureType::Texture1DArray:
|
||||
return SurfaceTarget::Texture1DArray;
|
||||
case Tegra::Texture::TextureType::Texture2DArray:
|
||||
return SurfaceTarget::Texture2DArray;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
return SurfaceTarget::Texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compression factor for the specified PixelFormat. This applies to just the
|
||||
* "compressed width" and "compressed height", not the overall compression factor of a
|
||||
@@ -635,7 +662,7 @@ struct SurfaceParams {
|
||||
ASSERT(width % compression_factor == 0);
|
||||
ASSERT(height % compression_factor == 0);
|
||||
return (width / compression_factor) * (height / compression_factor) *
|
||||
GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
GetFormatBpp(pixel_format) * depth / CHAR_BIT;
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
@@ -664,8 +691,10 @@ struct SurfaceParams {
|
||||
SurfaceType type;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 depth;
|
||||
u32 unaligned_height;
|
||||
size_t size_in_bytes;
|
||||
SurfaceTarget target;
|
||||
|
||||
// Parameters used for caching only
|
||||
u32 cache_width;
|
||||
@@ -709,6 +738,10 @@ public:
|
||||
return texture;
|
||||
}
|
||||
|
||||
GLenum Target() const {
|
||||
return gl_target;
|
||||
}
|
||||
|
||||
static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
|
||||
if (format == SurfaceParams::PixelFormat::Invalid)
|
||||
return 0;
|
||||
@@ -724,14 +757,14 @@ public:
|
||||
void LoadGLBuffer();
|
||||
void FlushGLBuffer();
|
||||
|
||||
// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
// Upload data in gl_buffer to this surface's texture
|
||||
void UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
void DownloadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
|
||||
private:
|
||||
OGLTexture texture;
|
||||
std::vector<u8> gl_buffer;
|
||||
SurfaceParams params;
|
||||
GLenum gl_target;
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
@@ -774,6 +807,10 @@ private:
|
||||
|
||||
OGLFramebuffer read_framebuffer;
|
||||
OGLFramebuffer draw_framebuffer;
|
||||
|
||||
/// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one
|
||||
/// using the new format.
|
||||
OGLBuffer copy_pbo;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -247,6 +247,7 @@ public:
|
||||
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix)
|
||||
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} {
|
||||
BuildRegisterList();
|
||||
BuildInputList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,9 +344,10 @@ public:
|
||||
* @param elem The element to use for the operation.
|
||||
* @param attribute The input attribute to use as the source value.
|
||||
*/
|
||||
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute) {
|
||||
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
|
||||
const Tegra::Shader::IpaMode& input_mode) {
|
||||
std::string dest = GetRegisterAsFloat(reg);
|
||||
std::string src = GetInputAttribute(attribute) + GetSwizzle(elem);
|
||||
std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
|
||||
shader.AddLine(dest + " = " + src + ';');
|
||||
}
|
||||
|
||||
@@ -412,12 +414,13 @@ public:
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
|
||||
for (const auto& index : declr_input_attribute) {
|
||||
for (const auto element : declr_input_attribute) {
|
||||
// TODO(bunnei): Use proper number of elements for these
|
||||
declarations.AddLine("layout(location = " +
|
||||
std::to_string(static_cast<u32>(index) -
|
||||
static_cast<u32>(Attribute::Index::Attribute_0)) +
|
||||
") in vec4 " + GetInputAttribute(index) + ';');
|
||||
u32 idx =
|
||||
static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0);
|
||||
declarations.AddLine("layout(location = " + std::to_string(idx) + ")" +
|
||||
GetInputFlags(element.first) + "in vec4 " +
|
||||
GetInputAttribute(element.first, element.second) + ';');
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
|
||||
@@ -440,13 +443,12 @@ public:
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
|
||||
// Append the sampler2D array for the used textures.
|
||||
const size_t num_samplers = used_samplers.size();
|
||||
if (num_samplers > 0) {
|
||||
declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' +
|
||||
std::to_string(num_samplers) + "];");
|
||||
declarations.AddNewLine();
|
||||
const auto& samplers = GetSamplers();
|
||||
for (const auto& sampler : samplers) {
|
||||
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
|
||||
';');
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Returns a list of constant buffer declarations
|
||||
@@ -458,13 +460,14 @@ public:
|
||||
}
|
||||
|
||||
/// Returns a list of samplers used in the shader
|
||||
std::vector<SamplerEntry> GetSamplers() const {
|
||||
const std::vector<SamplerEntry>& GetSamplers() const {
|
||||
return used_samplers;
|
||||
}
|
||||
|
||||
/// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
|
||||
/// necessary.
|
||||
std::string AccessSampler(const Sampler& sampler) {
|
||||
std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
|
||||
bool is_array) {
|
||||
size_t offset = static_cast<size_t>(sampler.index.Value());
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
@@ -473,12 +476,13 @@ public:
|
||||
[&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
|
||||
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array);
|
||||
return itr->GetName();
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
size_t next_index = used_samplers.size();
|
||||
SamplerEntry entry{stage, offset, next_index};
|
||||
SamplerEntry entry{stage, offset, next_index, type, is_array};
|
||||
used_samplers.emplace_back(entry);
|
||||
return entry.GetName();
|
||||
}
|
||||
@@ -532,11 +536,24 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void BuildInputList() {
|
||||
const u32 size = static_cast<u32>(Attribute::Index::Attribute_31) -
|
||||
static_cast<u32>(Attribute::Index::Attribute_0) + 1;
|
||||
declr_input_attribute.reserve(size);
|
||||
}
|
||||
|
||||
/// Generates code representing an input attribute register.
|
||||
std::string GetInputAttribute(Attribute::Index attribute) {
|
||||
std::string GetInputAttribute(Attribute::Index attribute,
|
||||
const Tegra::Shader::IpaMode& input_mode) {
|
||||
switch (attribute) {
|
||||
case Attribute::Index::Position:
|
||||
return "position";
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
|
||||
return "position";
|
||||
} else {
|
||||
return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)";
|
||||
}
|
||||
case Attribute::Index::PointCoord:
|
||||
return "vec4(gl_PointCoord.x, gl_PointCoord.y, 0, 0)";
|
||||
case Attribute::Index::TessCoordInstanceIDVertexID:
|
||||
// TODO(Subv): Find out what the values are for the first two elements when inside a
|
||||
// vertex shader, and what's the value of the fourth element when inside a Tess Eval
|
||||
@@ -552,7 +569,14 @@ private:
|
||||
static_cast<u32>(Attribute::Index::Attribute_0)};
|
||||
if (attribute >= Attribute::Index::Attribute_0 &&
|
||||
attribute <= Attribute::Index::Attribute_31) {
|
||||
declr_input_attribute.insert(attribute);
|
||||
if (declr_input_attribute.count(attribute) == 0) {
|
||||
declr_input_attribute[attribute] = input_mode;
|
||||
} else {
|
||||
if (declr_input_attribute[attribute] != input_mode) {
|
||||
LOG_CRITICAL(HW_GPU, "Same Input multiple input modes");
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
return "input_attribute_" + std::to_string(index);
|
||||
}
|
||||
|
||||
@@ -563,6 +587,49 @@ private:
|
||||
return "vec4(0, 0, 0, 0)";
|
||||
}
|
||||
|
||||
std::string GetInputFlags(const Attribute::Index attribute) {
|
||||
const Tegra::Shader::IpaSampleMode sample_mode =
|
||||
declr_input_attribute[attribute].sampling_mode;
|
||||
const Tegra::Shader::IpaInterpMode interp_mode =
|
||||
declr_input_attribute[attribute].interpolation_mode;
|
||||
std::string out;
|
||||
switch (interp_mode) {
|
||||
case Tegra::Shader::IpaInterpMode::Flat: {
|
||||
out += "flat ";
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::IpaInterpMode::Linear: {
|
||||
out += "noperspective ";
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::IpaInterpMode::Perspective: {
|
||||
// Default, Smooth
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled Ipa InterpMode: {}", static_cast<u32>(interp_mode));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
switch (sample_mode) {
|
||||
case Tegra::Shader::IpaSampleMode::Centroid: {
|
||||
// Note not implemented, it can be implemented with the "centroid " keyword in glsl;
|
||||
LOG_CRITICAL(HW_GPU, "Ipa Sampler Mode: centroid, not implemented");
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::IpaSampleMode::Default: {
|
||||
// Default, n/a
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled Ipa SampleMode: {}", static_cast<u32>(sample_mode));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Generates code representing an output attribute register.
|
||||
std::string GetOutputAttribute(Attribute::Index attribute) {
|
||||
switch (attribute) {
|
||||
@@ -593,7 +660,7 @@ private:
|
||||
ShaderWriter& shader;
|
||||
ShaderWriter& declarations;
|
||||
std::vector<GLSLRegister> regs;
|
||||
std::set<Attribute::Index> declr_input_attribute;
|
||||
std::unordered_map<Attribute::Index, Tegra::Shader::IpaMode> declr_input_attribute;
|
||||
std::set<Attribute::Index> declr_output_attribute;
|
||||
std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers;
|
||||
std::vector<SamplerEntry> used_samplers;
|
||||
@@ -656,8 +723,8 @@ private:
|
||||
}
|
||||
|
||||
/// Generates code representing a texture sampler.
|
||||
std::string GetSampler(const Sampler& sampler) {
|
||||
return regs.AccessSampler(sampler);
|
||||
std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) {
|
||||
return regs.AccessSampler(sampler, type, is_array);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1634,8 +1701,12 @@ private:
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::LD_A: {
|
||||
ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested");
|
||||
// Note: Shouldn't this be interp mode flat? As in no interpolation made.
|
||||
|
||||
Tegra::Shader::IpaMode input_mode{Tegra::Shader::IpaInterpMode::Perspective,
|
||||
Tegra::Shader::IpaSampleMode::Default};
|
||||
regs.SetRegisterToInputAttibute(instr.gpr0, instr.attribute.fmt20.element,
|
||||
instr.attribute.fmt20.index);
|
||||
instr.attribute.fmt20.index, input_mode);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::LD_C: {
|
||||
@@ -1683,10 +1754,35 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEX: {
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented");
|
||||
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
|
||||
std::string coord;
|
||||
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture1D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
coord = "float coords = " + x + ';';
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
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, texture_type, false);
|
||||
// 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("{");
|
||||
@@ -1708,20 +1804,65 @@ private:
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
std::string coord;
|
||||
Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()};
|
||||
bool is_array{instr.texs.IsArrayTexture()};
|
||||
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
if (is_array) {
|
||||
std::string index = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");";
|
||||
} else {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLDS: {
|
||||
const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsInteger(instr.gpr20);
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string coord = "ivec2 coords = ivec2(" + op_a + ", " + op_b + ");";
|
||||
ASSERT(instr.tlds.GetTextureType() == Tegra::Shader::TextureType::Texture2D);
|
||||
ASSERT(instr.tlds.IsArrayTexture() == false);
|
||||
std::string coord;
|
||||
|
||||
switch (instr.tlds.GetTextureType()) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
if (instr.tlds.IsArrayTexture()) {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture");
|
||||
UNREACHABLE();
|
||||
} else {
|
||||
std::string x = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsInteger(instr.gpr20);
|
||||
coord = "ivec2 coords = ivec2(" + x + ", " + y + ");";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(instr.tlds.GetTextureType()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
const std::string sampler = GetSampler(instr.sampler, instr.tlds.GetTextureType(),
|
||||
instr.tlds.IsArrayTexture());
|
||||
const std::string texture = "texelFetch(" + sampler + ", coords, 0)";
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
break;
|
||||
@@ -1729,7 +1870,7 @@ private:
|
||||
case OpCode::Id::TLD4: {
|
||||
ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D);
|
||||
ASSERT(instr.tld4.array == 0);
|
||||
std::string coord{};
|
||||
std::string coord;
|
||||
|
||||
switch (instr.tld4.texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
@@ -1744,7 +1885,8 @@ private:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const std::string sampler = GetSampler(instr.sampler);
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, instr.tld4.texture_type, false);
|
||||
// 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("{");
|
||||
@@ -1770,7 +1912,8 @@ private:
|
||||
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);
|
||||
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 texture = "textureGather(" + sampler + ", coords, " +
|
||||
std::to_string(instr.tld4s.component) + ')';
|
||||
@@ -2127,42 +2270,15 @@ private:
|
||||
case OpCode::Id::IPA: {
|
||||
const auto& attribute = instr.attribute.fmt28;
|
||||
const auto& reg = instr.gpr0;
|
||||
ASSERT_MSG(instr.ipa.sample_mode == Tegra::Shader::IpaSampleMode::Default,
|
||||
"Unhandled IPA sample mode: {}",
|
||||
static_cast<u32>(instr.ipa.sample_mode.Value()));
|
||||
ASSERT_MSG(instr.ipa.saturate == 0, "IPA saturate not implemented");
|
||||
switch (instr.ipa.interp_mode) {
|
||||
case Tegra::Shader::IpaInterpMode::Linear:
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Fragment &&
|
||||
attribute.index == Attribute::Index::Position) {
|
||||
switch (attribute.element) {
|
||||
case 0:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.x;");
|
||||
break;
|
||||
case 1:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.y;");
|
||||
break;
|
||||
case 2:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.z;");
|
||||
break;
|
||||
case 3:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = 1.0;");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index);
|
||||
}
|
||||
break;
|
||||
case Tegra::Shader::IpaInterpMode::Perspective:
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled IPA mode: {}",
|
||||
static_cast<u32>(instr.ipa.interp_mode.Value()));
|
||||
UNREACHABLE();
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index);
|
||||
}
|
||||
|
||||
Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(),
|
||||
instr.ipa.sample_mode.Value()};
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index,
|
||||
input_mode);
|
||||
|
||||
if (instr.ipa.saturate) {
|
||||
regs.SetRegisterToFloat(reg, 0, regs.GetRegisterAsFloat(reg), 1, 1, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SSY: {
|
||||
|
||||
@@ -42,6 +42,7 @@ layout (std140) uniform vs_config {
|
||||
};
|
||||
|
||||
void main() {
|
||||
position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
exec_vertex();
|
||||
)";
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
@@ -73,8 +74,9 @@ class SamplerEntry {
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
public:
|
||||
SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index)
|
||||
: offset(offset), stage(stage), sampler_index(index) {}
|
||||
SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index,
|
||||
Tegra::Shader::TextureType type, bool is_array)
|
||||
: offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {}
|
||||
|
||||
size_t GetOffset() const {
|
||||
return offset;
|
||||
@@ -89,8 +91,41 @@ public:
|
||||
}
|
||||
|
||||
std::string GetName() const {
|
||||
return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' +
|
||||
std::to_string(sampler_index) + ']';
|
||||
return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '_' +
|
||||
std::to_string(sampler_index);
|
||||
}
|
||||
|
||||
std::string GetTypeString() const {
|
||||
using Tegra::Shader::TextureType;
|
||||
std::string glsl_type;
|
||||
|
||||
switch (type) {
|
||||
case TextureType::Texture1D:
|
||||
glsl_type = "sampler1D";
|
||||
break;
|
||||
case TextureType::Texture2D:
|
||||
glsl_type = "sampler2D";
|
||||
break;
|
||||
case TextureType::Texture3D:
|
||||
glsl_type = "sampler3D";
|
||||
break;
|
||||
case TextureType::TextureCube:
|
||||
glsl_type = "samplerCube";
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
if (is_array)
|
||||
glsl_type += "Array";
|
||||
return glsl_type;
|
||||
}
|
||||
|
||||
Tegra::Shader::TextureType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
bool IsArray() const {
|
||||
return is_array;
|
||||
}
|
||||
|
||||
u32 GetHash() const {
|
||||
@@ -105,11 +140,14 @@ private:
|
||||
static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = {
|
||||
"tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs",
|
||||
};
|
||||
|
||||
/// Offset in TSC memory from which to read the sampler object, as specified by the sampling
|
||||
/// instruction.
|
||||
size_t offset;
|
||||
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
|
||||
size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
|
||||
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
|
||||
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.
|
||||
};
|
||||
|
||||
struct ShaderEntries {
|
||||
|
||||
@@ -200,9 +200,9 @@ void OpenGLState::Apply() const {
|
||||
const auto& texture_unit = texture_units[i];
|
||||
const auto& cur_state_texture_unit = cur_state.texture_units[i];
|
||||
|
||||
if (texture_unit.texture_2d != cur_state_texture_unit.texture_2d) {
|
||||
if (texture_unit.texture != cur_state_texture_unit.texture) {
|
||||
glActiveTexture(TextureUnits::MaxwellTexture(static_cast<int>(i)).Enum());
|
||||
glBindTexture(GL_TEXTURE_2D, texture_unit.texture_2d);
|
||||
glBindTexture(texture_unit.target, texture_unit.texture);
|
||||
}
|
||||
if (texture_unit.sampler != cur_state_texture_unit.sampler) {
|
||||
glBindSampler(static_cast<GLuint>(i), texture_unit.sampler);
|
||||
@@ -214,7 +214,7 @@ void OpenGLState::Apply() const {
|
||||
texture_unit.swizzle.a != cur_state_texture_unit.swizzle.a) {
|
||||
std::array<GLint, 4> mask = {texture_unit.swizzle.r, texture_unit.swizzle.g,
|
||||
texture_unit.swizzle.b, texture_unit.swizzle.a};
|
||||
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, mask.data());
|
||||
glTexParameteriv(texture_unit.target, GL_TEXTURE_SWIZZLE_RGBA, mask.data());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ void OpenGLState::Apply() const {
|
||||
|
||||
OpenGLState& OpenGLState::UnbindTexture(GLuint handle) {
|
||||
for (auto& unit : texture_units) {
|
||||
if (unit.texture_2d == handle) {
|
||||
if (unit.texture == handle) {
|
||||
unit.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,9 @@ public:
|
||||
|
||||
// 3 texture units - one for each that is used in PICA fragment shader emulation
|
||||
struct TextureUnit {
|
||||
GLuint texture_2d; // GL_TEXTURE_BINDING_2D
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
GLuint texture; // GL_TEXTURE_BINDING_2D
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
GLenum target;
|
||||
struct {
|
||||
GLint r; // GL_TEXTURE_SWIZZLE_R
|
||||
GLint g; // GL_TEXTURE_SWIZZLE_G
|
||||
@@ -104,7 +105,7 @@ public:
|
||||
} swizzle;
|
||||
|
||||
void Unbind() {
|
||||
texture_2d = 0;
|
||||
texture = 0;
|
||||
swizzle.r = GL_RED;
|
||||
swizzle.g = GL_GREEN;
|
||||
swizzle.b = GL_BLUE;
|
||||
@@ -114,6 +115,7 @@ public:
|
||||
void Reset() {
|
||||
Unbind();
|
||||
sampler = 0;
|
||||
target = GL_TEXTURE_2D;
|
||||
}
|
||||
};
|
||||
std::array<TextureUnit, 32> texture_units;
|
||||
|
||||
@@ -177,7 +177,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
Memory::GetPointer(framebuffer_addr),
|
||||
gl_framebuffer_data.data(), true);
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.texture_units[0].texture = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -194,7 +194,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
*/
|
||||
void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
|
||||
const TextureInfo& texture) {
|
||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
||||
state.texture_units[0].texture = texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -214,7 +214,7 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
|
||||
// Update existing texture
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
// Allocation of storage is deferred until the first frame, when we
|
||||
// know the framebuffer size.
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.texture_units[0].texture = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -272,7 +272,7 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
|
||||
// Clear screen to black
|
||||
@@ -305,14 +305,14 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
||||
state.texture_units[0].texture = texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0,
|
||||
texture.gl_format, texture.gl_type, nullptr);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
@@ -354,14 +354,14 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
|
||||
ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v),
|
||||
}};
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.display_texture;
|
||||
state.texture_units[0].texture = screen_info.display_texture;
|
||||
state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
|
||||
state.Apply();
|
||||
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.texture_units[0].texture = 0;
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -170,8 +170,12 @@ struct TICEntry {
|
||||
BitField<0, 16, u32> width_minus_1;
|
||||
BitField<23, 4, TextureType> texture_type;
|
||||
};
|
||||
u16 height_minus_1;
|
||||
INSERT_PADDING_BYTES(10);
|
||||
union {
|
||||
BitField<0, 16, u32> height_minus_1;
|
||||
BitField<16, 15, u32> depth_minus_1;
|
||||
};
|
||||
|
||||
INSERT_PADDING_BYTES(8);
|
||||
|
||||
GPUVAddr Address() const {
|
||||
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
|
||||
@@ -192,6 +196,10 @@ struct TICEntry {
|
||||
return height_minus_1 + 1;
|
||||
}
|
||||
|
||||
u32 Depth() const {
|
||||
return depth_minus_1 + 1;
|
||||
}
|
||||
|
||||
u32 BlockHeight() const {
|
||||
ASSERT(header_version == TICHeaderVersion::BlockLinear ||
|
||||
header_version == TICHeaderVersion::BlockLinearColorKey);
|
||||
|
||||
@@ -9,6 +9,8 @@ add_executable(yuzu
|
||||
about_dialog.h
|
||||
bootmanager.cpp
|
||||
bootmanager.h
|
||||
compatibility_list.cpp
|
||||
compatibility_list.h
|
||||
configuration/config.cpp
|
||||
configuration/config.h
|
||||
configuration/configure_audio.cpp
|
||||
@@ -43,6 +45,8 @@ add_executable(yuzu
|
||||
game_list.cpp
|
||||
game_list.h
|
||||
game_list_p.h
|
||||
game_list_worker.cpp
|
||||
game_list_worker.h
|
||||
hotkeys.cpp
|
||||
hotkeys.h
|
||||
main.cpp
|
||||
|
||||
@@ -11,7 +11,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
ui->setupUi(this);
|
||||
ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200));
|
||||
ui->labelBuildInfo->setText(
|
||||
ui->labelBuildInfo->text().arg(Common::g_build_name, Common::g_scm_branch,
|
||||
ui->labelBuildInfo->text().arg(Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc, QString(Common::g_build_date).left(10)));
|
||||
}
|
||||
|
||||
|
||||
@@ -256,6 +256,7 @@ void GRenderWindow::InitRenderTarget() {
|
||||
QGLFormat fmt;
|
||||
fmt.setVersion(3, 3);
|
||||
fmt.setProfile(QGLFormat::CoreProfile);
|
||||
fmt.setSwapInterval(false);
|
||||
|
||||
// Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
|
||||
fmt.setOption(QGL::NoDeprecatedFunctions);
|
||||
|
||||
18
src/yuzu/compatibility_list.cpp
Normal file
18
src/yuzu/compatibility_list.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
|
||||
const CompatibilityList& compatibility_list, u64 program_id) {
|
||||
return std::find_if(compatibility_list.begin(), compatibility_list.end(),
|
||||
[program_id](const auto& element) {
|
||||
std::string pid = fmt::format("{:016X}", program_id);
|
||||
return element.first == pid;
|
||||
});
|
||||
}
|
||||
17
src/yuzu/compatibility_list.h
Normal file
17
src/yuzu/compatibility_list.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
using CompatibilityList = std::unordered_map<std::string, std::pair<QString, QString>>;
|
||||
|
||||
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
|
||||
const CompatibilityList& compatibility_list, u64 program_id);
|
||||
@@ -18,16 +18,11 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/game_list_worker.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
@@ -232,6 +227,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
|
||||
item_model->insertColumns(0, COLUMN_COUNT);
|
||||
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
|
||||
item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility");
|
||||
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, "Add-ons");
|
||||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
|
||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
|
||||
|
||||
@@ -434,26 +430,6 @@ void GameList::LoadInterfaceLayout() {
|
||||
|
||||
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
|
||||
|
||||
static bool HasSupportedFileExtension(const std::string& file_name) {
|
||||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
||||
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
static bool IsExtractedNCAMain(const std::string& file_name) {
|
||||
return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
|
||||
}
|
||||
|
||||
static QString FormatGameName(const std::string& physical_name) {
|
||||
const QString physical_name_as_qstring = QString::fromStdString(physical_name);
|
||||
const QFileInfo file_info(physical_name_as_qstring);
|
||||
|
||||
if (IsExtractedNCAMain(physical_name)) {
|
||||
return file_info.dir().path();
|
||||
}
|
||||
|
||||
return physical_name_as_qstring;
|
||||
}
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
|
||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
||||
@@ -461,175 +437,3 @@ void GameList::RefreshGameDirectory() {
|
||||
PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
|
||||
std::vector<u8>& icon, std::string& name) {
|
||||
const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
|
||||
if (control_dir == nullptr)
|
||||
return;
|
||||
|
||||
const auto nacp_file = control_dir->GetFile("control.nacp");
|
||||
if (nacp_file == nullptr)
|
||||
return;
|
||||
FileSys::NACP nacp(nacp_file);
|
||||
name = nacp.GetApplicationName();
|
||||
|
||||
FileSys::VirtualFile icon_file = nullptr;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr) {
|
||||
icon = icon_file->ReadAllBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameListWorker::GameListWorker(
|
||||
FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
|
||||
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto& file = cache->GetEntryUnparsed(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
|
||||
std::vector<u8> icon;
|
||||
std::string name;
|
||||
u64 program_id = 0;
|
||||
loader->ReadProgramId(program_id);
|
||||
|
||||
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(control, icon, name);
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(file->GetSize()),
|
||||
});
|
||||
}
|
||||
|
||||
const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
const auto nca = cache->GetEntry(entry);
|
||||
if (nca != nullptr)
|
||||
nca_control_map.insert_or_assign(entry.title_id, nca);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
QFileInfo file_info(physical_name.c_str());
|
||||
if (!is_dir && file_info.suffix().toStdString() == "nca") {
|
||||
auto nca =
|
||||
std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (nca->GetType() == FileSys::NCAContentType::Control)
|
||||
nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
std::unique_ptr<Loader::AppLoader> loader =
|
||||
Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
|
||||
loader->GetFileType() == Loader::FileType::Error) &&
|
||||
!UISettings::values.show_unknown))
|
||||
return true;
|
||||
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(nca, icon, name);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(physical_name), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
});
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
||||
}
|
||||
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents());
|
||||
AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents());
|
||||
AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents());
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
void GameListWorker::Cancel() {
|
||||
this->disconnect();
|
||||
stop_processing = true;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -21,6 +19,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
class GameListWorker;
|
||||
class GMainWindow;
|
||||
@@ -38,6 +37,7 @@ public:
|
||||
enum {
|
||||
COLUMN_NAME,
|
||||
COLUMN_COMPATIBILITY,
|
||||
COLUMN_ADD_ONS,
|
||||
COLUMN_FILE_TYPE,
|
||||
COLUMN_SIZE,
|
||||
COLUMN_COUNT, // Number of columns
|
||||
@@ -89,9 +89,8 @@ signals:
|
||||
void GameChosen(QString game_path);
|
||||
void ShouldCancelWorker();
|
||||
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
|
||||
void NavigateToGamedbEntryRequested(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
void NavigateToGamedbEntryRequested(u64 program_id,
|
||||
const CompatibilityList& compatibility_list);
|
||||
|
||||
private slots:
|
||||
void onTextChanged(const QString& newText);
|
||||
@@ -113,7 +112,7 @@ private:
|
||||
QStandardItemModel* item_model = nullptr;
|
||||
GameListWorker* current_worker = nullptr;
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list;
|
||||
CompatibilityList compatibility_list;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(GameListOpenTarget);
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -16,7 +14,6 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
#include <QStandardItem>
|
||||
#include <QString>
|
||||
|
||||
@@ -26,12 +23,6 @@
|
||||
#include "yuzu/ui_settings.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
namespace FileSys {
|
||||
class NCA;
|
||||
class RegisteredCache;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
/**
|
||||
* Gets the default icon (for games without valid SMDH)
|
||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||
@@ -43,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
static auto FindMatchingCompatibilityEntry(
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
|
||||
u64 program_id) {
|
||||
return std::find_if(
|
||||
compatibility_list.begin(), compatibility_list.end(),
|
||||
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||
std::string pid = fmt::format("{:016X}", program_id);
|
||||
return element.first == pid;
|
||||
});
|
||||
}
|
||||
|
||||
class GameListItem : public QStandardItem {
|
||||
|
||||
public:
|
||||
@@ -196,50 +176,3 @@ public:
|
||||
return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronous worker object for populating the game list.
|
||||
* Communicates with other threads through Qt's signal/slot system.
|
||||
*/
|
||||
class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
public slots:
|
||||
/// Starts the processing of directory tree information.
|
||||
void run() override;
|
||||
/// Tells the worker that it should no longer continue processing. Thread-safe.
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
|
||||
* to be added to the game list.
|
||||
* @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
|
||||
*/
|
||||
void EntryReady(QList<QStandardItem*> entry_items);
|
||||
|
||||
/**
|
||||
* After the worker has traversed the game directory looking for entries, this signal is emmited
|
||||
* with a list of folders that should be watched for changes as well.
|
||||
*/
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache);
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
};
|
||||
|
||||
239
src/yuzu/game_list_worker.cpp
Normal file
239
src/yuzu/game_list_worker.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/game_list_worker.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
namespace {
|
||||
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
|
||||
const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
|
||||
std::string& name) {
|
||||
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
|
||||
if (icon_file != nullptr)
|
||||
icon = icon_file->ReadAllBytes();
|
||||
if (nacp != nullptr)
|
||||
name = nacp->GetApplicationName();
|
||||
}
|
||||
|
||||
bool HasSupportedFileExtension(const std::string& file_name) {
|
||||
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
|
||||
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool IsExtractedNCAMain(const std::string& file_name) {
|
||||
return QFileInfo(QString::fromStdString(file_name)).fileName() == "main";
|
||||
}
|
||||
|
||||
QString FormatGameName(const std::string& physical_name) {
|
||||
const QString physical_name_as_qstring = QString::fromStdString(physical_name);
|
||||
const QFileInfo file_info(physical_name_as_qstring);
|
||||
|
||||
if (IsExtractedNCAMain(physical_name)) {
|
||||
return file_info.dir().path();
|
||||
}
|
||||
|
||||
return physical_name_as_qstring;
|
||||
}
|
||||
|
||||
QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
|
||||
QString out;
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames()) {
|
||||
if (!updatable && kv.first == FileSys::PatchType::Update)
|
||||
continue;
|
||||
|
||||
if (kv.second.empty()) {
|
||||
out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
|
||||
} else {
|
||||
out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
out.chop(1);
|
||||
return out;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto cache = Service::FileSystem::GetUnionContents();
|
||||
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto& file = cache->GetEntryUnparsed(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
|
||||
std::vector<u8> icon;
|
||||
std::string name;
|
||||
u64 program_id = 0;
|
||||
loader->ReadProgramId(program_id);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(patch, control, icon, name);
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch)),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(file->GetSize()),
|
||||
});
|
||||
}
|
||||
|
||||
const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
const auto nca = cache->GetEntry(entry);
|
||||
if (nca != nullptr)
|
||||
nca_control_map.insert_or_assign(entry.title_id, nca);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
QFileInfo file_info(physical_name.c_str());
|
||||
if (!is_dir && file_info.suffix().toStdString() == "nca") {
|
||||
auto nca =
|
||||
std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (nca->GetType() == FileSys::NCAContentType::Control)
|
||||
nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (stop_processing)
|
||||
return false; // Breaks the callback loop.
|
||||
|
||||
bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
std::unique_ptr<Loader::AppLoader> loader =
|
||||
Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
|
||||
loader->GetFileType() == Loader::FileType::Error) &&
|
||||
!UISettings::values.show_unknown))
|
||||
return true;
|
||||
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, nca, icon, name);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(physical_name), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
});
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback);
|
||||
}
|
||||
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
void GameListWorker::Cancel() {
|
||||
this->disconnect();
|
||||
stop_processing = true;
|
||||
}
|
||||
72
src/yuzu/game_list_worker.h
Normal file
72
src/yuzu/game_list_worker.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
#include <QString>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
||||
class QStandardItem;
|
||||
|
||||
namespace FileSys {
|
||||
class NCA;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
/**
|
||||
* Asynchronous worker object for populating the game list.
|
||||
* Communicates with other threads through Qt's signal/slot system.
|
||||
*/
|
||||
class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
/// Starts the processing of directory tree information.
|
||||
void run() override;
|
||||
|
||||
/// Tells the worker that it should no longer continue processing. Thread-safe.
|
||||
void Cancel();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* The `EntryReady` signal is emitted once an entry has been prepared and is ready
|
||||
* to be added to the game list.
|
||||
* @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
|
||||
*/
|
||||
void EntryReady(QList<QStandardItem*> entry_items);
|
||||
|
||||
/**
|
||||
* After the worker has traversed the game directory looking for entries, this signal is emitted
|
||||
* with a list of folders that should be watched for changes as well.
|
||||
*/
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
void AddInstalledTitlesToGameList();
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
const CompatibilityList& compatibility_list;
|
||||
std::atomic_bool stop_processing;
|
||||
};
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
@@ -45,6 +47,7 @@
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "yuzu/about_dialog.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/debugger/console.h"
|
||||
@@ -134,11 +137,11 @@ GMainWindow::GMainWindow()
|
||||
|
||||
ConnectMenuEvents();
|
||||
ConnectWidgetEvents();
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch,
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
||||
setWindowTitle(QString("yuzu %1| %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
|
||||
show();
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
@@ -442,6 +445,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
|
||||
unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev");
|
||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
||||
unsupported_ext.append("ARB_texture_mirror_clamp_to_edge");
|
||||
if (!GLAD_GL_ARB_base_instance)
|
||||
unsupported_ext.append("ARB_base_instance");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
@@ -592,11 +597,19 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
|
||||
std::string title_name;
|
||||
const auto res = Core::System::GetInstance().GetGameName(title_name);
|
||||
if (res != Loader::ResultStatus::Success)
|
||||
title_name = FileUtil::GetFilename(filename.toStdString());
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
const u64 program_id = Core::System::GetInstance().CurrentProcess()->program_id;
|
||||
|
||||
const auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
|
||||
if (nacp != nullptr)
|
||||
title_name = nacp->GetApplicationName();
|
||||
|
||||
if (title_name.empty())
|
||||
title_name = FileUtil::GetFilename(filename.toStdString());
|
||||
}
|
||||
|
||||
setWindowTitle(QString("yuzu %1| %4 | %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc,
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
|
||||
QString::fromStdString(title_name)));
|
||||
|
||||
render_window->show();
|
||||
@@ -631,7 +644,7 @@ void GMainWindow::ShutdownGame() {
|
||||
game_list->show();
|
||||
game_list->setFilterFocus();
|
||||
setWindowTitle(QString("yuzu %1| %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
|
||||
|
||||
// Disable status bar updates
|
||||
status_bar_update_timer.stop();
|
||||
@@ -713,14 +726,11 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListNavigateToGamedbEntry(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) {
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
|
||||
const CompatibilityList& compatibility_list) {
|
||||
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
QString directory;
|
||||
|
||||
if (it != compatibility_list.end())
|
||||
directory = it->second.second;
|
||||
|
||||
@@ -868,7 +878,11 @@ void GMainWindow::OnMenuInstallToNAND() {
|
||||
} else {
|
||||
const auto nca = std::make_shared<FileSys::NCA>(
|
||||
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
const auto id = nca->GetStatus();
|
||||
|
||||
// Game updates necessary are missing base RomFS
|
||||
if (id != Loader::ResultStatus::Success &&
|
||||
id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "ui_main.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
|
||||
class Config;
|
||||
@@ -137,9 +138,8 @@ private slots:
|
||||
/// Called whenever a user selects a game in the game list widget.
|
||||
void OnGameListLoadFile(QString game_path);
|
||||
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
|
||||
void OnGameListNavigateToGamedbEntry(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
void OnGameListNavigateToGamedbEntry(u64 program_id,
|
||||
const CompatibilityList& compatibility_list);
|
||||
void OnMenuLoadFile();
|
||||
void OnMenuLoadFolder();
|
||||
void OnMenuInstallToNAND();
|
||||
|
||||
@@ -91,6 +91,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
|
||||
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
|
||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
||||
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
||||
if (!GLAD_GL_ARB_base_instance)
|
||||
unsupported_ext.push_back("ARB_base_instance");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
@@ -128,7 +130,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
@@ -166,7 +168,8 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch,
|
||||
SDL_GL_SetSwapInterval(false);
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
|
||||
DoneCurrent();
|
||||
|
||||
Reference in New Issue
Block a user