Compare commits

...

72 Commits

Author SHA1 Message Date
Lioncash
ddcdbce067 gl_buffer_cache: Default initialize member variables
Ensures that the cache always has a deterministic initial state.
2018-09-06 15:07:15 -04:00
Lioncash
8d685a29bc gl_buffer_cache: Make GetHandle() a const member function
GetHandle() internally calls GetHandle() on the stream_buffer instance,
which is a const member function, so this can be made const as well.
2018-09-06 15:07:15 -04:00
Lioncash
14230fe2af gl_buffer_cache: Remove unnecessary includes 2018-09-06 15:05:52 -04:00
Lioncash
68296d9474 gl_buffer_cache: Make constructor explicit
Implicit conversions during construction isn't desirable here.
2018-09-06 14:54:49 -04:00
bunnei
fbaefc47a0 Merge pull request #1248 from degasus/shader_fix
gl_shader_gen: Initialize position.
2018-09-06 13:06:09 -04:00
Markus Wick
a781042700 gl_shader_gen: Initialize position.
IMO the old code is fine, but nvidia raises shader compiler warnings.
Trivial fix through...
2018-09-06 13:37:50 +02:00
bunnei
77554ac773 Merge pull request #1243 from degasus/VAO_cache
gl_rasterizer: Implement a VAO cache.
2018-09-05 22:50:52 -04:00
bunnei
6f09c5b128 Merge pull request #1244 from FernandoS27/ipa
shader_decompiler: Implemented IPA Properly (Stage 1)
2018-09-05 21:20:40 -04:00
FernandoS27
e63b229f4a Implemented IPA Properly 2018-09-05 20:15:47 -04:00
bunnei
94f193af65 Merge pull request #1242 from lioncash/file-sys
file_sys/submission_package: Replace includes with forward declarations where applicable
2018-09-05 18:53:32 -04:00
bunnei
a6ae765410 Merge pull request #1179 from DarkLordZach/bktr
file_sys: Add support for BKTR format (Game Updates)
2018-09-05 18:06:11 -04:00
bunnei
aba988f71c Merge pull request #1245 from degasus/optimizations
gl_rasterizer: Skip TODO log.
2018-09-05 16:13:46 -04:00
Markus Wick
7f15306f78 gl_rasterizer: Skip TODO log.
This is called ~3k times per frame in SMO ingame.
My laptop spends ~3ms per frame on allocating and freeing this string.

Let's just stop printing this kind of redundant information.
2018-09-05 20:20:20 +02:00
Lioncash
6bd6beee20 file_sys/submission_package: Correct constructor initialization list order
Orders the elements in the sequence to match the order in which they'll
actually be initialized in.
2018-09-05 13:44:42 -04:00
Markus Wick
d3ad9469a1 gl_rasterizer: Implement a VAO cache.
This patch caches VAO objects instead of re-emiting all pointers per draw call.
Configuring this pointers is known as a fast task, but it yields too many GL
calls. So for better performance, just bind the VAO instead of 16 pointers.
2018-09-05 18:46:35 +02:00
Lioncash
c0b7ed8b58 file_sys/submission_package: Replace includes with forward declarations where applicable 2018-09-05 12:08:04 -04:00
bunnei
527e362a83 Merge pull request #1217 from degasus/vbo_cache2
renderer_opengl: Implement a buffer cache.
2018-09-05 11:35:31 -04:00
Markus Wick
50a806ea67 renderer_opengl: Implement a buffer cache.
The idea of this cache is to avoid redundant uploads. So we are going
to cache the uploaded buffers within the stream_buffer and just reuse
the old pointers.
The next step is to implement a VBO cache on GPU memory, but for now,
I want to check the overhead of the cache management. Fetching the
buffer over PCI-E should be quite fast.
2018-09-05 08:03:50 +02:00
bunnei
a1ef02c3e6 Merge pull request #1240 from degasus/optimizations
gl_shader_cache: Use an u32 for the binding point cache.
2018-09-04 18:20:14 -04:00
Zach Hilman
c913136eb2 bktr: Fix bucket overlap error 2018-09-04 17:01:54 -04:00
Zach Hilman
7d5d781b20 drd: Parse title ID from program metadata 2018-09-04 16:25:30 -04:00
Zach Hilman
23a16c1720 patch_manager: Centralize Control-type NCA parsing 2018-09-04 16:25:10 -04:00
Zach Hilman
92e26df00f nsp: Fix error masking issue with XCI files
Now display correct error instead of catch-all MissingProgramNCA
2018-09-04 16:24:24 -04:00
Zach Hilman
c91b60a421 game_list: Fix version display on non-NAND titles 2018-09-04 16:24:02 -04:00
Zach Hilman
cbd517d8cc bktr: Add logging on successful patch 2018-09-04 16:24:02 -04:00
Zach Hilman
2814ca3624 game_list: Use friendly game versions
Mainly, from control.nacp metadata instead of cnmt metadata
2018-09-04 16:24:02 -04:00
Zach Hilman
a6e75cd45b bktr: Implement IVFC offset shifting
Fixes base game read errors
2018-09-04 16:24:02 -04:00
Zach Hilman
9664ce255d bktr: Fix missing includes and optimize style 2018-09-04 16:24:02 -04:00
Zach Hilman
f92b3512e0 main: Make game updates installable 2018-09-04 16:24:02 -04:00
Zach Hilman
8e150c46b9 game_list: Display patch names and versions on list 2018-09-04 16:24:02 -04:00
Zach Hilman
f5e03b9173 loader: Add BKTR-specific error messages and codes 2018-09-04 16:23:44 -04:00
Zach Hilman
08fcb4694f loader: Ignore patches on NRO and DRD 2018-09-04 16:23:15 -04:00
Zach Hilman
97bf83bc56 patch_manager: Add usages of patches to ExeFS 2018-09-04 16:23:15 -04:00
Zach Hilman
8e900a301a file_sys: Add class to manage game patches
Right now only includes Updates, but should eventually contain all of the other patches we need.
2018-09-04 16:22:25 -04:00
Zach Hilman
54e7ddb93a file_sys: Add BKTR patching mechanism 2018-09-04 16:22:25 -04:00
Zach Hilman
1efe5a76b1 content_archive: Add BKTR header parsing to NCA 2018-09-04 16:22:25 -04:00
Zach Hilman
9951f6d054 registration: Add RegisteredCacheUnion
Aggregates multiple caches into one interface
2018-09-04 16:21:40 -04:00
Zach Hilman
d2caf4af7d game_list: Use RegisteredCacheUnion for installed
Reduces code
2018-09-04 16:21:40 -04:00
Zach Hilman
99fbcb3bf2 aes_util: Fix error involving reads of less than 0x10
Issues with block size are fixed by making all reads minimum length of 0x10
2018-09-04 16:21:40 -04:00
bunnei
faa9e066ab Merge pull request #1178 from DarkLordZach/nsp
file_sys: Add Nintendo Submissions Package (NSP) file format
2018-09-04 16:20:40 -04:00
Markus Wick
99a71580c4 gl_shader_cache: Use an u32 for the binding point cache.
The std::string generation with its malloc and free requirement
was a noticeable overhead. Also switch to an ordered_map to
avoid the std::hash call. As those maps usually have a size of
two elements, the lookup time shall not matter.
2018-09-04 21:04:41 +02:00
Zach Hilman
87be4bc283 main: Only show DRD deprecation warning once 2018-09-04 14:44:48 -04:00
Zach Hilman
e973cceadd control_metadata: Use alternate language names if AmericanEnglish isn't available 2018-09-04 14:30:03 -04:00
Zach Hilman
23d2c50479 card_image: Add program title ID getter 2018-09-04 14:29:19 -04:00
Zach Hilman
1280061725 qt: Add deprecation warnings for DRD format 2018-09-04 14:29:19 -04:00
Zach Hilman
8974771334 registration: Fix NSP installation errors 2018-09-04 14:29:19 -04:00
Zach Hilman
e4e55d064e nsp: Comply with style and performance guidelines 2018-09-04 14:29:19 -04:00
Zach Hilman
58473309a0 qt: Add UI support for NSP files 2018-09-04 14:28:41 -04:00
Zach Hilman
f7eaea424d registration: Add support for installing NSP files 2018-09-04 14:28:41 -04:00
Zach Hilman
d7518cf6e0 loader: Add AppLoader for NSP files 2018-09-04 14:27:33 -04:00
Zach Hilman
5c8aff984e card_image: Parse XCI secure partition with NSP
Eliminated duplicate code and adds support for Rev1+ carts
2018-09-04 14:27:33 -04:00
Zach Hilman
93703431e2 file_sys: Add Nintendo Submission Package (NSP) 2018-09-04 14:25:54 -04:00
Zach Hilman
a040929c90 drd: Load title ID from program metadata
Previously only loaded from control metadata
2018-09-04 14:25:54 -04:00
Zach Hilman
b555311438 loader: Add NSP file type and NSP-specific errors 2018-09-04 14:25:54 -04:00
Zach Hilman
d770c60205 key_manager: Avoid autogeneration if key exists 2018-09-04 14:25:54 -04:00
bunnei
dda4b5e89e Merge pull request #1238 from lioncash/explicit
common/logging: Minor changes
2018-09-04 12:18:00 -04:00
bunnei
9a07e9f805 Merge pull request #1237 from degasus/optimizations
Optimizations
2018-09-04 12:16:06 -04:00
bunnei
ed37b68fb5 Merge pull request #1223 from DarkLordZach/custom-nand-sd-dirs
file_sys: Allow for custom NAND/SD directories
2018-09-04 11:54:22 -04:00
bunnei
26e96d16d0 Merge pull request #1232 from lioncash/copy
gl_shader_decompiler: Use used_shaders member variable directly within GenerateDeclarations()
2018-09-04 11:52:25 -04:00
bunnei
8ec1e16867 Merge pull request #1235 from lioncash/forward-decl
file_sys: Replace includes with forward declarations where applicable
2018-09-04 11:51:54 -04:00
bunnei
5a29b358aa Merge pull request #1236 from degasus/microprofile
Update microprofile scopes.
2018-09-04 11:50:58 -04:00
bunnei
c156ee8eb8 Merge pull request #1230 from lioncash/ssl
ssl: Move SSL class to cpp file
2018-09-04 11:49:22 -04:00
Lioncash
6ef84f1c4c common/logging: Amend documentation comments
Multi-line doc comments still need the '<' after the ///, otherwise it's
treated as a regular comment and makes the original doc comment broken
in viewers, IDEs, etc. While we're at it, also fix some typos in the
comments.
2018-09-04 10:49:08 -04:00
Lioncash
2949d9552c common/logging/filter: Replace C-style case with C++ static_cast 2018-09-04 10:44:36 -04:00
Lioncash
978f3a3282 common/logging/filter: Make constructor explicit
Implicit conversions aren't desirable here.
2018-09-04 10:43:31 -04:00
Markus Wick
10bc725944 Update microprofile scopes.
Blame the subsystems which deserve the blame :)

The updated list is not complete, just the ones I've spotted on random sampling the stack trace.
2018-09-04 11:04:26 +02:00
Lioncash
a813c10e1c file_sys: Replace includes with forward declarations where applicable
Cuts down on include dependencies, resulting in less files that need to
be rebuilt when certain things are changed.
2018-09-03 22:52:24 -04:00
Zach Hilman
04397cd185 qt: Add message about not moving contents on dir change 2018-09-03 19:23:33 -04:00
Zach Hilman
1ff3318458 qt: Add UI options to change NAND/SD dirs 2018-09-03 19:23:33 -04:00
Zach Hilman
b2268f1f8d settings: Save and load NAND/SD dirs from config 2018-09-03 19:23:33 -04:00
Lioncash
18a89931a9 gl_shader_decompiler: Use used_shaders member variable directly within GenerateDeclarations()
Using the getter function intended for external code here makes an
unnecessary copy of the already-accessible used_shaders vector.
2018-09-02 13:10:11 -04:00
Lioncash
41cd766438 ssl: Move SSL class to cpp file
This isn't required to be visible to anything outside of the main source
file, and will eliminate needing to rebuild anything else including the
header if the SSL class needs to be changed in the future.
2018-09-02 11:45:26 -04:00
75 changed files with 2343 additions and 395 deletions

View File

@@ -10,6 +10,7 @@
#include "audio_core/stream.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/settings.h"
@@ -94,7 +95,10 @@ void Stream::PlayNextBuffer() {
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
}
MICROPROFILE_DEFINE(AudioOutput, "Audio", "ReleaseActiveBuffer", MP_RGB(100, 100, 255));
void Stream::ReleaseActiveBuffer() {
MICROPROFILE_SCOPE(AudioOutput);
ASSERT(active_buffer);
released_buffers.push(std::move(active_buffer));
release_callback();

View File

@@ -19,7 +19,7 @@ namespace Log {
class Filter {
public:
/// Initializes the filter with all classes having `default_level` as the minimum level.
Filter(Level default_level = Level::Info);
explicit Filter(Level default_level = Level::Info);
/// Resets the filter so that all classes have `level` as the minimum displayed level.
void ResetAll(Level level);
@@ -49,6 +49,6 @@ public:
bool IsDebug() const;
private:
std::array<Level, (size_t)Class::Count> class_levels;
std::array<Level, static_cast<size_t>(Class::Count)> class_levels;
};
} // namespace Log

View File

@@ -12,14 +12,14 @@ namespace Log {
/// Specifies the severity or level of detail of the log message.
enum class Level : u8 {
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
/// pollute logs.
///< pollute logs.
Debug, ///< Less detailed debugging information.
Info, ///< Status information from important points during execution.
Warning, ///< Minor or potential problems found during execution of a task.
Error, ///< Major problems found during execution of a task that prevent it from being
/// completed.
Critical, ///< Major problems during execution that threathen the stability of the entire
/// application.
///< completed.
Critical, ///< Major problems during execution that threaten the stability of the entire
///< application.
Count ///< Total number of logging levels
};
@@ -49,7 +49,7 @@ enum class Class : ClassType {
Kernel, ///< The HLE implementation of the CTR kernel
Kernel_SVC, ///< Kernel system calls
Service, ///< HLE implementation of system services. Each major service
/// should have its own subclass.
///< should have its own subclass.
Service_ACC, ///< The ACC (Accounts) service
Service_AM, ///< The AM (Applet manager) service
Service_AOC, ///< The AOC (AddOn Content) service

View File

@@ -15,6 +15,6 @@ struct Entry;
std::string FormatLogMessage(const Entry& entry);
/// Formats and prints a log entry to stderr.
void PrintMessage(const Entry& entry);
/// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry& entry);
} // namespace Log

View File

@@ -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
@@ -49,6 +53,8 @@ add_library(core STATIC
file_sys/savedata_factory.h
file_sys/sdmc_factory.cpp
file_sys/sdmc_factory.h
file_sys/submission_package.cpp
file_sys/submission_package.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
@@ -359,6 +365,8 @@ add_library(core STATIC
loader/nro.h
loader/nso.cpp
loader/nso.h
loader/nsp.cpp
loader/nsp.h
loader/xci.cpp
loader/xci.h
memory.cpp

View File

@@ -7,6 +7,7 @@
#include <dynarmic/A64/a64.h>
#include <dynarmic/A64/config.h>
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/core.h"
#include "core/core_cpu.h"
@@ -143,7 +144,10 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
return std::make_unique<Dynarmic::A64::Jit>(config);
}
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
void ARM_Dynarmic::Run() {
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic);
ASSERT(Memory::GetCurrentPageTable() == current_page_table);
jit->Run();

View File

@@ -193,10 +193,10 @@ void ARM_Unicorn::Step() {
ExecuteInstructions(1);
}
MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64));
void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
MICROPROFILE_SCOPE(ARM_Jit);
MICROPROFILE_SCOPE(ARM_Jit_Unicorn);
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
CoreTiming::AddTicks(num_instructions);
if (GDBStub::IsServerEnabled()) {

View File

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

View File

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

View File

@@ -8,12 +8,15 @@
#include <locale>
#include <sstream>
#include <string_view>
#include <tuple>
#include <vector>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Core::Crypto {
@@ -228,18 +231,28 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
const auto iter = std::find_if(
if (s128_keys.find({id, field1, field2}) != s128_keys.end())
return;
if (id == S128KeyType::Titlekey) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
}
const auto iter2 = std::find_if(
s128_file_id.begin(), s128_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
std::tie(id, field1, field2);
});
if (iter != s128_file_id.end())
WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key);
if (iter2 != s128_file_id.end())
WriteKeyToFile(false, iter2->first, key);
s128_keys[{id, field1, field2}] = key;
}
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
if (s256_keys.find({id, field1, field2}) != s256_keys.end())
return;
const auto iter = std::find_if(
s256_file_id.begin(), s256_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {

View File

@@ -6,16 +6,19 @@
#include <array>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <boost/container/flat_map.hpp>
#include <boost/optional.hpp>
#include <fmt/format.h>
#include "common/common_types.h"
#include "core/loader/loader.h"
namespace Loader {
enum class ResultStatus : u16;
}
namespace Core::Crypto {
constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/registered_cache.h"
namespace FileSys {
@@ -13,6 +14,8 @@ BISFactory::BISFactory(VirtualDir nand_root_)
usrnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
BISFactory::~BISFactory() = default;
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
return sysnand_cache;
}

View File

@@ -5,17 +5,20 @@
#pragma once
#include <memory>
#include "core/loader/loader.h"
#include "registered_cache.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class RegisteredCache;
/// File system interface to the Built-In Storage
/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
/// registered caches.
class BISFactory {
public:
explicit BISFactory(VirtualDir nand_root);
~BISFactory();
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;

View File

@@ -9,7 +9,10 @@
#include "common/logging/log.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
@@ -43,15 +46,19 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
}
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
secure_partition = std::make_shared<NSP>(
main_hfs.GetFile(partition_names[static_cast<size_t>(XCIPartition::Secure)]));
auto result = AddNCAFromPartition(XCIPartition::Secure);
if (result != Loader::ResultStatus::Success) {
status = result;
return;
}
const auto secure_ncas = secure_partition->GetNCAsCollapsed();
std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
result = AddNCAFromPartition(XCIPartition::Update);
program =
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
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) {
status = result;
return;
@@ -74,6 +81,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
status = Loader::ResultStatus::Success;
}
XCI::~XCI() = default;
Loader::ResultStatus XCI::GetStatus() const {
return status;
}
@@ -86,6 +95,10 @@ VirtualDir XCI::GetPartition(XCIPartition partition) const {
return partitions[static_cast<size_t>(partition)];
}
std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
return secure_partition;
}
VirtualDir XCI::GetSecurePartition() const {
return GetPartition(XCIPartition::Secure);
}
@@ -102,6 +115,20 @@ VirtualDir XCI::GetLogoPartition() const {
return GetPartition(XCIPartition::Logo);
}
u64 XCI::GetProgramTitleID() const {
return secure_partition->GetProgramTitleID();
}
std::shared_ptr<NCA> XCI::GetProgramNCA() const {
return program;
}
VirtualFile XCI::GetProgramNCAFile() const {
if (GetProgramNCA() == nullptr)
return nullptr;
return GetProgramNCA()->GetBaseFile();
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
return ncas;
}

View File

@@ -5,15 +5,22 @@
#pragma once
#include <array>
#include <memory>
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace Loader {
enum class ResultStatus : u16;
}
namespace FileSys {
class NCA;
enum class NCAContentType : u8;
class NSP;
enum class GamecardSize : u8 {
S_1GB = 0xFA,
S_2GB = 0xF8,
@@ -57,6 +64,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
class XCI : public ReadOnlyVfsDirectory {
public:
explicit XCI(VirtualFile file);
~XCI() override;
Loader::ResultStatus GetStatus() const;
Loader::ResultStatus GetProgramNCAStatus() const;
@@ -64,11 +72,16 @@ public:
u8 GetFormatVersion() const;
VirtualDir GetPartition(XCIPartition partition) const;
std::shared_ptr<NSP> GetSecurePartitionNSP() const;
VirtualDir GetSecurePartition() const;
VirtualDir GetNormalPartition() const;
VirtualDir GetUpdatePartition() const;
VirtualDir GetLogoPartition() const;
u64 GetProgramTitleID() const;
std::shared_ptr<NCA> GetProgramNCA() const;
VirtualFile GetProgramNCAFile() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
VirtualFile GetNCAFileByType(NCAContentType type) const;
@@ -94,6 +107,8 @@ private:
Loader::ResultStatus program_nca_status;
std::vector<VirtualDir> partitions;
std::shared_ptr<NSP> secure_partition;
std::shared_ptr<NCA> program;
std::vector<std::shared_ptr<NCA>> ncas;
};
} // namespace FileSys

View File

@@ -3,12 +3,17 @@
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include <utility>
#include <boost/optional.hpp>
#include "common/logging/log.h"
#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"
#include "core/loader/loader.h"
@@ -64,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.");
@@ -100,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}",
@@ -150,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;
@@ -186,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) {
@@ -261,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)
@@ -285,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) +
@@ -300,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)
@@ -345,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;
}
@@ -362,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) {

View File

@@ -12,10 +12,12 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "control_metadata.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/loader/loader.h"
#include "core/file_sys/vfs.h"
namespace Loader {
enum class ResultStatus : u16;
}
namespace FileSys {
@@ -77,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;
@@ -87,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;
@@ -110,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;
};

View File

@@ -21,7 +21,17 @@ NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
}
const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
return raw->language_entries.at(static_cast<u8>(language));
if (language != Language::Default) {
return raw->language_entries.at(static_cast<u8>(language));
} else {
for (const auto& language_entry : raw->language_entries) {
if (!language_entry.GetApplicationName().empty())
return language_entry;
}
// Fallback to English
return GetLanguageEntry(Language::AmericanEnglish);
}
}
std::string NACP::GetApplicationName(Language language) const {

View File

@@ -8,6 +8,8 @@
#include <memory>
#include <string>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -60,6 +62,8 @@ enum class Language : u8 {
Korean = 12,
Taiwanese = 13,
Chinese = 14,
Default = 255,
};
static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
@@ -74,9 +78,9 @@ static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
class NACP {
public:
explicit NACP(VirtualFile file);
const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const;
std::string GetApplicationName(Language language = Language::AmericanEnglish) const;
std::string GetDeveloperName(Language language = Language::AmericanEnglish) const;
const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const;
std::string GetApplicationName(Language language = Language::Default) const;
std::string GetDeveloperName(Language language = Language::Default) const;
u64 GetTitleId() const;
std::string GetVersionString() const;

View File

@@ -3,10 +3,9 @@
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "content_archive.h"
#include "core/file_sys/nca_metadata.h"
namespace FileSys {

View File

@@ -4,7 +4,6 @@
#pragma once
#include <cstring>
#include <memory>
#include <vector>
#include "common/common_funcs.h"

View File

@@ -0,0 +1,206 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#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_)
: base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
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

View File

@@ -0,0 +1,147 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <vector>
#include <common/common_funcs.h>
#include "core/crypto/key_manager.h"
#include "core/file_sys/romfs.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

View File

@@ -0,0 +1,153 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#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

View File

@@ -0,0 +1,62 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#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

View File

@@ -2,7 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/file_util.h"
#include <cstddef>
#include <cstring>
#include <vector>
#include "common/logging/log.h"
#include "core/file_sys/program_metadata.h"
#include "core/loader/loader.h"

View File

@@ -5,12 +5,10 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "partition_filesystem.h"
#include "core/file_sys/vfs.h"
namespace Loader {
enum class ResultStatus : u16;

View File

@@ -8,10 +8,14 @@
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_concat.h"
#include "core/loader/loader.h"
namespace FileSys {
std::string RegisteredCacheEntry::DebugInfo() const {
@@ -276,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)
@@ -355,17 +371,21 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
return out;
}
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false));
const auto iter =
std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
[&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
return iter == xci->GetNCAs().end() ? nullptr : *iter;
static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) {
const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
if (file == nullptr)
return nullptr;
return std::make_shared<NCA>(file);
}
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
const auto& ncas = xci->GetNCAs();
return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy);
}
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
const auto& ncas = nsp->GetNCAsCollapsed();
const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
return nca->GetType() == NCAContentType::Meta;
});
@@ -389,7 +409,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overw
const auto cnmt_file = section0->GetFiles()[0];
const CNMT cnmt(cnmt_file);
for (const auto& record : cnmt.GetContentRecords()) {
const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
if (nca == nullptr)
return InstallResult::ErrorCopyFailed;
const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
@@ -490,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

View File

@@ -11,15 +11,19 @@
#include <string>
#include <vector>
#include <boost/container/flat_map.hpp>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class XCI;
class CNMT;
class NCA;
class NSP;
class XCI;
enum class ContentRecordType : u8;
enum class TitleType : u8;
struct ContentRecord;
using NcaID = std::array<u8, 0x10>;
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
@@ -39,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);
@@ -56,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
@@ -70,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;
@@ -86,10 +98,12 @@ public:
boost::optional<ContentRecordType> record_type = boost::none,
boost::optional<u64> title_id = boost::none) const;
// Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
// is a meta NCA and all of them are accessible.
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// there is a meta NCA and all of them are accessible.
InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
// Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
// poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
@@ -125,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

View File

@@ -6,6 +6,7 @@
#include <array>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"

View File

@@ -2,11 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#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"
@@ -20,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) {

View File

@@ -36,6 +36,8 @@ public:
private:
VirtualFile file;
bool updatable;
u64 ivfc_offset;
};
} // namespace FileSys

View File

@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <memory>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"

View File

@@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"

View File

@@ -0,0 +1,245 @@
// Copyright 2018 yuzu emulator team
// 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/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"
#include "core/file_sys/submission_package.h"
#include "core/loader/loader.h"
namespace FileSys {
NSP::NSP(VirtualFile file_)
: file(std::move(file_)), status{Loader::ResultStatus::Success},
pfs(std::make_shared<PartitionFilesystem>(file)) {
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
status = pfs->GetStatus();
return;
}
if (IsDirectoryExeFS(pfs)) {
extracted = true;
exefs = pfs;
const auto& files = pfs->GetFiles();
const auto romfs_iter =
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
return file->GetName().find(".romfs") != std::string::npos;
});
if (romfs_iter != files.end())
romfs = *romfs_iter;
return;
}
extracted = false;
const auto files = pfs->GetFiles();
Core::Crypto::KeyManager keys;
for (const auto& ticket_file : files) {
if (ticket_file->GetExtension() == "tik") {
if (ticket_file == nullptr ||
ticket_file->GetSize() <
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
continue;
}
Core::Crypto::Key128 key{};
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
std::string_view name_only(ticket_file->GetName());
name_only.remove_suffix(4);
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
u128 rights_id;
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
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) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
if (inner_file->GetExtension() != "cnmt")
continue;
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
ncas_title[ContentRecordType::Meta] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
LOG_WARNING(Service_FS,
"NCA with ID {}.nca is listed in content metadata, but cannot "
"be found in PFS. NSP appears to be corrupted.",
id_string);
continue;
}
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
ncas_title[rec.type] = std::move(next_nca);
}
break;
}
}
}
}
NSP::~NSP() = default;
Loader::ResultStatus NSP::GetStatus() const {
return status;
}
Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
const auto iter = program_status.find(title_id);
if (iter == program_status.end())
return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
return iter->second;
}
u64 NSP::GetFirstTitleID() const {
if (program_status.empty())
return 0;
return program_status.begin()->first;
}
u64 NSP::GetProgramTitleID() const {
const auto out = GetFirstTitleID();
if ((out & 0x800) == 0)
return out;
const auto ids = GetTitleIDs();
const auto iter =
std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; });
return iter == ids.end() ? out : *iter;
}
std::vector<u64> NSP::GetTitleIDs() const {
std::vector<u64> out;
out.reserve(ncas.size());
for (const auto& kv : ncas)
out.push_back(kv.first);
return out;
}
bool NSP::IsExtractedType() const {
return extracted;
}
VirtualFile NSP::GetRomFS() const {
return romfs;
}
VirtualDir NSP::GetExeFS() const {
return exefs;
}
std::vector<std::shared_ptr<NCA>> NSP::GetNCAsCollapsed() const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
std::vector<std::shared_ptr<NCA>> out;
for (const auto& map : ncas) {
for (const auto& inner_map : map.second)
out.push_back(inner_map.second);
}
return out;
}
std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
std::multimap<u64, std::shared_ptr<NCA>> out;
for (const auto& map : ncas) {
for (const auto& inner_map : map.second)
out.emplace(map.first, inner_map.second);
}
return out;
}
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
return ncas;
}
std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
const auto title_id_iter = ncas.find(title_id);
if (title_id_iter == ncas.end())
return nullptr;
const auto type_iter = title_id_iter->second.find(type);
if (type_iter == title_id_iter->second.end())
return nullptr;
return type_iter->second;
}
VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
const auto nca = GetNCA(title_id, type);
if (nca != nullptr)
return nca->GetBaseFile();
return nullptr;
}
std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
std::vector<Core::Crypto::Key128> out;
for (const auto& ticket_file : ticket_files) {
if (ticket_file == nullptr ||
ticket_file->GetSize() <
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
continue;
}
out.emplace_back();
ticket_file->Read(out.back().data(), out.back().size(),
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
}
return out;
}
std::vector<VirtualFile> NSP::GetFiles() const {
return pfs->GetFiles();
}
std::vector<VirtualDir> NSP::GetSubdirectories() const {
return pfs->GetSubdirectories();
}
std::string NSP::GetName() const {
return file->GetName();
}
VirtualDir NSP::GetParentDirectory() const {
return file->GetContainingDirectory();
}
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -0,0 +1,76 @@
// 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 <vector>
#include "common/common_types.h"
#include "core/file_sys/vfs.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);
~NSP();
Loader::ResultStatus GetStatus() const;
Loader::ResultStatus GetProgramStatus(u64 title_id) const;
// Should only be used when one title id can be assured.
u64 GetFirstTitleID() const;
u64 GetProgramTitleID() const;
std::vector<u64> GetTitleIDs() const;
bool IsExtractedType() const;
// Common (Can be safely called on both types)
VirtualFile GetRomFS() const;
VirtualDir GetExeFS() const;
// Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
std::vector<Core::Crypto::Key128> GetTitlekey() const;
std::vector<VirtualFile> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
VirtualDir GetParentDirectory() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
VirtualFile file;
bool extracted;
Loader::ResultStatus status;
std::map<u64, Loader::ResultStatus> program_status;
std::shared_ptr<PartitionFilesystem> pfs;
// Map title id -> {map type -> NCA}
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
VirtualFile romfs;
VirtualDir exefs;
};
} // namespace FileSys

View File

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

View File

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

View File

@@ -5,7 +5,9 @@
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/filesystem/filesystem.h"

View File

@@ -3,6 +3,9 @@
// Refer to the license.txt file included.
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/ssl/ssl.h"
namespace Service::SSL {
@@ -81,36 +84,43 @@ private:
}
};
void SSL::CreateContext(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED) called");
class SSL final : public ServiceFramework<SSL> {
public:
explicit SSL() : ServiceFramework{"ssl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &SSL::CreateContext, "CreateContext"},
{1, nullptr, "GetContextCount"},
{2, nullptr, "GetCertificates"},
{3, nullptr, "GetCertificateBufSize"},
{4, nullptr, "DebugIoctl"},
{5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"},
{6, nullptr, "FlushSessionCache"},
};
// clang-format on
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISslContext>();
}
RegisterHandlers(functions);
}
SSL::SSL() : ServiceFramework("ssl") {
static const FunctionInfo functions[] = {
{0, &SSL::CreateContext, "CreateContext"},
{1, nullptr, "GetContextCount"},
{2, nullptr, "GetCertificates"},
{3, nullptr, "GetCertificateBufSize"},
{4, nullptr, "DebugIoctl"},
{5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"},
{6, nullptr, "FlushSessionCache"},
};
RegisterHandlers(functions);
}
private:
void CreateContext(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED) called");
void SSL::SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 unk1 = rp.Pop<u32>(); // Probably minor/major?
u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<ISslContext>();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED) called");
IPC::RequestParser rp{ctx};
u32 unk1 = rp.Pop<u32>(); // Probably minor/major?
u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<SSL>()->InstallAsService(service_manager);

View File

@@ -4,20 +4,12 @@
#pragma once
#include "core/hle/service/service.h"
namespace Service::SM {
class ServiceManager;
}
namespace Service::SSL {
class SSL final : public ServiceFramework<SSL> {
public:
explicit SSL();
~SSL() = default;
private:
void CreateContext(Kernel::HLERequestContext& ctx);
void SetInterfaceVersion(Kernel::HLERequestContext& ctx);
};
/// Registers all SSL services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager);

View File

@@ -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) {
@@ -61,14 +71,14 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
if (nacp_file != nullptr) {
FileSys::NACP nacp(nacp_file);
title_id = nacp.GetTitleId();
name = nacp.GetApplicationName();
}
}
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())) {
@@ -90,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;
@@ -98,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()};
@@ -159,8 +185,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buff
}
ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) {
if (name.empty())
return ResultStatus::ErrorNoControl;
out_program_id = title_id;
return ResultStatus::Success;
}
@@ -172,4 +196,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadTitle(std::string& title)
return ResultStatus::Success;
}
bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const {
return false;
}
} // namespace Loader

View File

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

View File

@@ -15,6 +15,7 @@
#include "core/loader/nca.h"
#include "core/loader/nro.h"
#include "core/loader/nso.h"
#include "core/loader/nsp.h"
#include "core/loader/xci.h"
namespace Loader {
@@ -34,6 +35,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
CHECK_TYPE(NCA)
CHECK_TYPE(XCI)
CHECK_TYPE(NAX)
CHECK_TYPE(NSP)
#undef CHECK_TYPE
@@ -59,6 +61,8 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::NCA;
if (extension == "xci")
return FileType::XCI;
if (extension == "nsp")
return FileType::NSP;
return FileType::Unknown;
}
@@ -77,6 +81,8 @@ std::string GetFileTypeString(FileType type) {
return "XCI";
case FileType::NAX:
return "NAX";
case FileType::NSP:
return "NSP";
case FileType::DeconstructedRomDirectory:
return "Directory";
case FileType::Error:
@@ -87,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
constexpr std::array<const char*, 49> 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.",
@@ -137,6 +143,15 @@ constexpr std::array<const char*, 49> 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 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) {
@@ -182,6 +197,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
case FileType::NAX:
return std::make_unique<AppLoader_NAX>(std::move(file));
// NX NSP (Nintendo Submission Package) file format
case FileType::NSP:
return std::make_unique<AppLoader_NSP>(std::move(file));
// NX deconstructed ROM directory.
case FileType::DeconstructedRomDirectory:
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));

View File

@@ -29,6 +29,7 @@ enum class FileType {
NSO,
NRO,
NCA,
NSP,
XCI,
NAX,
DeconstructedRomDirectory,
@@ -105,6 +106,15 @@ enum class ResultStatus : u16 {
ErrorMissingAESKeyGenerationSource,
ErrorMissingSDSaveKeySource,
ErrorMissingSDNCAKeySource,
ErrorNSPMissingProgramNCA,
ErrorBadBKTRHeader,
ErrorBKTRSubsectionNotAfterRelocation,
ErrorBKTRSubsectionNotAtEnd,
ErrorBadRelocationBlock,
ErrorBadSubsectionBlock,
ErrorBadRelocationBuckets,
ErrorBadSubsectionBuckets,
ErrorMissingBKTRBaseRomFS,
};
std::ostream& operator<<(std::ostream& os, ResultStatus status);
@@ -195,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;
}
/**

View File

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

View File

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

View File

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

View File

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

125
src/core/loader/nsp.cpp Normal file
View File

@@ -0,0 +1,125 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <vector>
#include "common/common_types.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/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"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
#include "core/loader/nsp.h"
namespace Loader {
AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
: AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
title_id(nsp->GetProgramTitleID()) {
if (nsp->GetStatus() != ResultStatus::Success)
return;
if (nsp->IsExtractedType())
return;
const auto control_nca =
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
return;
std::tie(nacp_file, icon_file) =
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca);
}
AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
FileSys::NSP nsp(file);
if (nsp.GetStatus() == ResultStatus::Success) {
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) {
return FileType::NSP;
}
// Non-Ectracted Type case
if (!nsp.IsExtractedType() &&
nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(nsp.GetNCAFile(
nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA) {
return FileType::NSP;
}
}
return FileType::Error;
}
ResultStatus AppLoader_NSP::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
}
if (nsp->IsExtractedType()) {
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
} else {
if (title_id == 0)
return ResultStatus::ErrorNSPMissingProgramNCA;
secondary_loader = std::make_unique<AppLoader_NCA>(
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
if (nsp->GetStatus() != ResultStatus::Success)
return nsp->GetStatus();
if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
return nsp->GetProgramStatus(title_id);
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
if (!Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
return ResultStatus::ErrorNSPMissingProgramNCA;
}
}
const auto result = secondary_loader->Load(process);
if (result != ResultStatus::Success)
return result;
is_loaded = true;
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) {
return secondary_loader->ReadRomFS(dir);
}
ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {
if (title_id == 0)
return ResultStatus::ErrorNotInitialized;
out_program_id = title_id;
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadIcon(std::vector<u8>& buffer) {
if (icon_file == nullptr)
return ResultStatus::ErrorNoControl;
buffer = icon_file->ReadAllBytes();
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
if (nacp_file == nullptr)
return ResultStatus::ErrorNoControl;
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
} // namespace Loader

54
src/core/loader/nsp.h Normal file
View File

@@ -0,0 +1,54 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace FileSys {
class NACP;
class NSP;
} // namespace FileSys
namespace Loader {
class AppLoader_NCA;
/// Loads an XCI file
class AppLoader_NSP final : public AppLoader {
public:
explicit AppLoader_NSP(FileSys::VirtualFile file);
~AppLoader_NSP() override;
/**
* Returns the type of the file
* @param file std::shared_ptr<VfsFile> open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() override {
return IdentifyType(file);
}
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
private:
std::unique_ptr<FileSys::NSP> nsp;
std::unique_ptr<AppLoader> secondary_loader;
FileSys::VirtualFile icon_file;
std::shared_ptr<FileSys::NACP> nacp_file;
u64 title_id;
};
} // namespace Loader

View File

@@ -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"
@@ -17,25 +19,16 @@ namespace Loader {
AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
: AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
nca_loader(std::make_unique<AppLoader_NCA>(
xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {
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;
@@ -64,11 +57,11 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (xci->GetProgramNCAStatus() != ResultStatus::Success)
return xci->GetProgramNCAStatus();
const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program);
const auto nca = xci->GetProgramNCA();
if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
auto result = nca_loader->Load(process);
const auto result = nca_loader->Load(process);
if (result != ResultStatus::Success)
return result;

View File

@@ -127,6 +127,8 @@ struct Values {
// Data Storage
bool use_virtual_sd;
std::string nand_dir;
std::string sdmc_dir;
// Renderer
float resolution_factor;

View File

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

View File

@@ -22,6 +22,7 @@ add_library(video_core STATIC
rasterizer_interface.h
renderer_base.cpp
renderer_base.h
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_rasterizer_cache.cpp

View File

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

View File

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

View File

@@ -0,0 +1,92 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <memory>
#include "common/alignment.h"
#include "core/core.h"
#include "core/memory.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
namespace OpenGL {
OGLBufferCache::OGLBufferCache(size_t size) : stream_buffer(GL_ARRAY_BUFFER, size) {}
GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, size_t size, size_t alignment,
bool cache) {
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
// Cache management is a big overhead, so only cache entries with a given size.
// TODO: Figure out which size is the best for given games.
cache &= size >= 2048;
if (cache) {
auto entry = TryGet(*cpu_addr);
if (entry) {
if (entry->size >= size && entry->alignment == alignment) {
return entry->offset;
}
Unregister(entry);
}
}
AlignBuffer(alignment);
GLintptr uploaded_offset = buffer_offset;
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
buffer_ptr += size;
buffer_offset += size;
if (cache) {
auto entry = std::make_shared<CachedBufferEntry>();
entry->offset = uploaded_offset;
entry->size = size;
entry->alignment = alignment;
entry->addr = *cpu_addr;
Register(entry);
}
return uploaded_offset;
}
GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, size_t size, size_t alignment) {
AlignBuffer(alignment);
std::memcpy(buffer_ptr, raw_pointer, size);
GLintptr uploaded_offset = buffer_offset;
buffer_ptr += size;
buffer_offset += size;
return uploaded_offset;
}
void OGLBufferCache::Map(size_t max_size) {
bool invalidate;
std::tie(buffer_ptr, buffer_offset_base, invalidate) =
stream_buffer.Map(static_cast<GLsizeiptr>(max_size), 4);
buffer_offset = buffer_offset_base;
if (invalidate) {
InvalidateAll();
}
}
void OGLBufferCache::Unmap() {
stream_buffer.Unmap(buffer_offset - buffer_offset_base);
}
GLuint OGLBufferCache::GetHandle() const {
return stream_buffer.GetHandle();
}
void OGLBufferCache::AlignBuffer(size_t alignment) {
// Align the offset, not the mapped pointer
GLintptr offset_aligned =
static_cast<GLintptr>(Common::AlignUp(static_cast<size_t>(buffer_offset), alignment));
buffer_ptr += offset_aligned - buffer_offset;
buffer_offset = offset_aligned;
}
} // namespace OpenGL

View File

@@ -0,0 +1,57 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <memory>
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
namespace OpenGL {
struct CachedBufferEntry final {
VAddr GetAddr() const {
return addr;
}
size_t GetSizeInBytes() const {
return size;
}
VAddr addr;
size_t size;
GLintptr offset;
size_t alignment;
};
class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
public:
explicit OGLBufferCache(size_t size);
GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, size_t size, size_t alignment = 4,
bool cache = true);
GLintptr UploadHostMemory(const void* raw_pointer, size_t size, size_t alignment = 4);
void Map(size_t max_size);
void Unmap();
GLuint GetHandle() const;
protected:
void AlignBuffer(size_t alignment);
private:
OGLStreamBuffer stream_buffer;
u8* buffer_ptr = nullptr;
GLintptr buffer_offset = 0;
GLintptr buffer_offset_base = 0;
};
} // namespace OpenGL

View File

@@ -33,14 +33,17 @@ using PixelFormat = SurfaceParams::PixelFormat;
using SurfaceType = SurfaceParams::SurfaceType;
MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_FS, "OpenGL", "Fragment Shader Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Shader, "OpenGL", "Shader Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_UBO, "OpenGL", "Const Buffer Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Index, "OpenGL", "Index Buffer Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Texture, "OpenGL", "Texture Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: emu_window{window}, screen_info{info}, stream_buffer(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE) {
: emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) {
// Create sampler objects
for (size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
@@ -67,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 = stream_buffer.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, stream_buffer.GetHandle());
glEnable(GL_BLEND);
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
@@ -98,14 +86,60 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
RasterizerOpenGL::~RasterizerOpenGL() {}
std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
GLintptr buffer_offset) {
void RasterizerOpenGL::SetupVertexArrays() {
MICROPROFILE_SCOPE(OpenGL_VAO);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
const auto& regs = gpu.regs;
state.draw.vertex_array = hw_vao.handle;
state.draw.vertex_buffer = stream_buffer.GetHandle();
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();
// Upload all guest vertex arrays sequentially to our buffer
@@ -124,12 +158,10 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
ASSERT(end > start);
u64 size = end - start + 1;
GLintptr vertex_buffer_offset;
std::tie(array_ptr, buffer_offset, vertex_buffer_offset) =
UploadMemory(array_ptr, buffer_offset, start, size);
GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size);
// Bind the vertex array to the buffer at the current offset.
glBindVertexBuffer(index, stream_buffer.GetHandle(), vertex_buffer_offset,
glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset,
vertex_array.stride);
if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
@@ -142,43 +174,10 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
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);
}
return {array_ptr, buffer_offset};
}
std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
void RasterizerOpenGL::SetupShaders() {
MICROPROFILE_SCOPE(OpenGL_Shader);
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
@@ -195,21 +194,15 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr
continue;
}
std::tie(buffer_ptr, buffer_offset) =
AlignBuffer(buffer_ptr, buffer_offset, static_cast<size_t>(uniform_buffer_alignment));
const size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5
GLShader::MaxwellUniformData ubo{};
ubo.SetFromRegs(gpu.state.shader_stages[stage]);
std::memcpy(buffer_ptr, &ubo, sizeof(ubo));
GLintptr offset = buffer_cache.UploadHostMemory(
&ubo, sizeof(ubo), static_cast<size_t>(uniform_buffer_alignment));
// Bind the buffer
glBindBufferRange(GL_UNIFORM_BUFFER, stage, stream_buffer.GetHandle(), buffer_offset,
sizeof(ubo));
buffer_ptr += sizeof(ubo);
buffer_offset += sizeof(ubo);
glBindBufferRange(GL_UNIFORM_BUFFER, stage, buffer_cache.GetHandle(), offset, sizeof(ubo));
Shader shader{shader_cache.GetStageProgram(program)};
@@ -230,9 +223,8 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr
}
// Configure the const buffers for this shader stage.
std::tie(buffer_ptr, buffer_offset, current_constbuffer_bindpoint) =
SetupConstBuffers(buffer_ptr, buffer_offset, static_cast<Maxwell::ShaderStage>(stage),
shader, current_constbuffer_bindpoint);
current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage),
shader, current_constbuffer_bindpoint);
// Configure the textures for this shader stage.
current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader,
@@ -246,8 +238,6 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr
}
shader_program_manager->UseTrivialGeometryShader();
return {buffer_ptr, buffer_offset};
}
size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
@@ -312,6 +302,7 @@ void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb,
bool using_depth_fb,
bool preserve_contents) {
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
if (regs.rt[0].format == Tegra::RenderTargetFormat::NONE) {
@@ -434,31 +425,6 @@ void RasterizerOpenGL::Clear() {
glClear(clear_mask);
}
std::pair<u8*, GLintptr> RasterizerOpenGL::AlignBuffer(u8* buffer_ptr, GLintptr buffer_offset,
size_t alignment) {
// Align the offset, not the mapped pointer
GLintptr offset_aligned =
static_cast<GLintptr>(Common::AlignUp(static_cast<size_t>(buffer_offset), alignment));
return {buffer_ptr + (offset_aligned - buffer_offset), offset_aligned};
}
std::tuple<u8*, GLintptr, GLintptr> RasterizerOpenGL::UploadMemory(u8* buffer_ptr,
GLintptr buffer_offset,
Tegra::GPUVAddr gpu_addr,
size_t size, size_t alignment) {
std::tie(buffer_ptr, buffer_offset) = AlignBuffer(buffer_ptr, buffer_offset, alignment);
GLintptr uploaded_offset = buffer_offset;
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
buffer_ptr += size;
buffer_offset += size;
return {buffer_ptr, buffer_offset, uploaded_offset};
}
void RasterizerOpenGL::DrawArrays() {
if (accelerate_draw == AccelDraw::Disabled)
return;
@@ -484,7 +450,7 @@ void RasterizerOpenGL::DrawArrays() {
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
state.draw.vertex_buffer = stream_buffer.GetHandle();
state.draw.vertex_buffer = buffer_cache.GetHandle();
state.Apply();
size_t buffer_size = CalculateVertexArraysSize();
@@ -501,24 +467,21 @@ void RasterizerOpenGL::DrawArrays() {
// Add space for at least 18 constant buffers
buffer_size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + uniform_buffer_alignment);
u8* buffer_ptr;
GLintptr buffer_offset;
std::tie(buffer_ptr, buffer_offset, std::ignore) =
stream_buffer.Map(static_cast<GLsizeiptr>(buffer_size), 4);
u8* buffer_ptr_base = buffer_ptr;
buffer_cache.Map(buffer_size);
std::tie(buffer_ptr, buffer_offset) = SetupVertexArrays(buffer_ptr, buffer_offset);
SetupVertexArrays();
// If indexed mode, copy the index buffer
GLintptr index_buffer_offset = 0;
if (is_indexed) {
std::tie(buffer_ptr, buffer_offset, index_buffer_offset) = UploadMemory(
buffer_ptr, buffer_offset, regs.index_array.StartAddress(), index_buffer_size);
MICROPROFILE_SCOPE(OpenGL_Index);
index_buffer_offset =
buffer_cache.UploadMemory(regs.index_array.StartAddress(), index_buffer_size);
}
std::tie(buffer_ptr, buffer_offset) = SetupShaders(buffer_ptr, buffer_offset);
SetupShaders();
stream_buffer.Unmap(buffer_ptr - buffer_ptr_base);
buffer_cache.Unmap();
shader_program_manager->ApplyTo(state);
state.Apply();
@@ -563,6 +526,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
res_cache.InvalidateRegion(addr, size);
shader_cache.InvalidateRegion(addr, size);
buffer_cache.InvalidateRegion(addr, size);
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
@@ -652,11 +616,9 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
}
}
std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(u8* buffer_ptr,
GLintptr buffer_offset,
Maxwell::ShaderStage stage,
Shader& shader,
u32 current_bindpoint) {
u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader,
u32 current_bindpoint) {
MICROPROFILE_SCOPE(OpenGL_UBO);
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)];
@@ -692,26 +654,25 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(u8* buffer_pt
size = Common::AlignUp(size, sizeof(GLvec4));
ASSERT_MSG(size <= MaxConstbufferSize, "Constbuffer too big");
GLintptr const_buffer_offset;
std::tie(buffer_ptr, buffer_offset, const_buffer_offset) =
UploadMemory(buffer_ptr, buffer_offset, buffer.address, size,
static_cast<size_t>(uniform_buffer_alignment));
GLintptr const_buffer_offset = buffer_cache.UploadMemory(
buffer.address, size, static_cast<size_t>(uniform_buffer_alignment));
glBindBufferRange(GL_UNIFORM_BUFFER, current_bindpoint + bindpoint,
stream_buffer.GetHandle(), const_buffer_offset, size);
buffer_cache.GetHandle(), const_buffer_offset, size);
// Now configure the bindpoint of the buffer inside the shader
glUniformBlockBinding(shader->GetProgramHandle(),
shader->GetProgramResourceIndex(used_buffer.GetName()),
shader->GetProgramResourceIndex(used_buffer),
current_bindpoint + bindpoint);
}
state.Apply();
return {buffer_ptr, buffer_offset, current_bindpoint + static_cast<u32>(entries.size())};
return current_bindpoint + static_cast<u32>(entries.size());
}
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) {
MICROPROFILE_SCOPE(OpenGL_Texture);
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
const auto& entries = shader->GetShaderEntries().texture_samplers;
@@ -725,7 +686,7 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
// Bind the uniform to the sampler.
glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry.GetName()),
glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry),
current_bindpoint);
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());

View File

@@ -6,6 +6,7 @@
#include <array>
#include <cstddef>
#include <map>
#include <memory>
#include <tuple>
#include <utility>
@@ -18,7 +19,9 @@
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_cache.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
@@ -109,9 +112,8 @@ private:
* @param current_bindpoint The offset at which to start counting new buffer bindpoints.
* @returns The next available bindpoint for use in the next shader stage.
*/
std::tuple<u8*, GLintptr, u32> SetupConstBuffers(
u8* buffer_ptr, GLintptr buffer_offset, Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
Shader& shader, u32 current_bindpoint);
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
u32 current_bindpoint);
/*
* Configures the current textures to use for the draw command.
@@ -167,28 +169,23 @@ 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;
OGLStreamBuffer stream_buffer;
OGLBuffer uniform_buffer;
OGLBufferCache buffer_cache;
OGLFramebuffer framebuffer;
GLint uniform_buffer_alignment;
size_t CalculateVertexArraysSize() const;
std::pair<u8*, GLintptr> SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset);
void SetupVertexArrays();
std::pair<u8*, GLintptr> SetupShaders(u8* buffer_ptr, GLintptr buffer_offset);
std::pair<u8*, GLintptr> AlignBuffer(u8* buffer_ptr, GLintptr buffer_offset, size_t alignment);
std::tuple<u8*, GLintptr, GLintptr> UploadMemory(u8* buffer_ptr, GLintptr buffer_offset,
Tegra::GPUVAddr gpu_addr, size_t size,
size_t alignment = 4);
void SetupShaders();
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;

View File

@@ -690,7 +690,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool usin
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
// TODO(bunnei): This is hard corded to use just the first render buffer
LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!");
LOG_TRACE(Render_OpenGL, "hard-coded for render target 0!");
// get color and depth surfaces
SurfaceParams color_params{};

View File

@@ -85,23 +85,23 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
SetShaderUniformBlockBindings(program.handle);
}
GLuint CachedShader::GetProgramResourceIndex(const std::string& name) {
auto search{resource_cache.find(name)};
GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
auto search{resource_cache.find(buffer.GetHash())};
if (search == resource_cache.end()) {
const GLuint index{
glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, name.c_str())};
resource_cache[name] = index;
glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, buffer.GetName().c_str())};
resource_cache[buffer.GetHash()] = index;
return index;
}
return search->second;
}
GLint CachedShader::GetUniformLocation(const std::string& name) {
auto search{uniform_cache.find(name)};
GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) {
auto search{uniform_cache.find(sampler.GetHash())};
if (search == uniform_cache.end()) {
const GLint index{glGetUniformLocation(program.handle, name.c_str())};
uniform_cache[name] = index;
const GLint index{glGetUniformLocation(program.handle, sampler.GetName().c_str())};
uniform_cache[sampler.GetHash()] = index;
return index;
}

View File

@@ -4,8 +4,8 @@
#pragma once
#include <map>
#include <memory>
#include <unordered_map>
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
@@ -43,10 +43,10 @@ public:
}
/// Gets the GL program resource location for the specified resource, caching as needed
GLuint GetProgramResourceIndex(const std::string& name);
GLuint GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer);
/// Gets the GL uniform location for the specified resource, caching as needed
GLint GetUniformLocation(const std::string& name);
GLint GetUniformLocation(const GLShader::SamplerEntry& sampler);
private:
VAddr addr;
@@ -55,8 +55,8 @@ private:
GLShader::ShaderEntries entries;
OGLProgram program;
std::unordered_map<std::string, GLuint> resource_cache;
std::unordered_map<std::string, GLint> uniform_cache;
std::map<u32, GLuint> resource_cache;
std::map<u32, GLint> uniform_cache;
};
class ShaderCacheOpenGL final : public RasterizerCache<Shader> {

View File

@@ -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();
@@ -441,7 +444,7 @@ public:
declarations.AddNewLine();
// Append the sampler2D array for the used textures.
size_t num_samplers = GetSamplers().size();
const size_t num_samplers = used_samplers.size();
if (num_samplers > 0) {
declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' +
std::to_string(num_samplers) + "];");
@@ -532,11 +535,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 +568,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 +586,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 +659,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;
@@ -1634,8 +1700,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: {
@@ -2127,42 +2197,11 @@ 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);
break;
}
case OpCode::Id::SSY: {

View File

@@ -42,6 +42,7 @@ layout (std140) uniform vs_config {
};
void main() {
position = vec4(0.0, 0.0, 0.0, 0.0);
exec_vertex();
)";

View File

@@ -53,6 +53,10 @@ public:
return BufferBaseNames[static_cast<size_t>(stage)] + std::to_string(index);
}
u32 GetHash() const {
return (static_cast<u32>(stage) << 16) | index;
}
private:
static constexpr std::array<const char*, Maxwell::MaxShaderStage> BufferBaseNames = {
"buffer_vs_c", "buffer_tessc_c", "buffer_tesse_c", "buffer_gs_c", "buffer_fs_c",
@@ -89,6 +93,10 @@ public:
std::to_string(sampler_index) + ']';
}
u32 GetHash() const {
return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index);
}
static std::string GetArrayName(Maxwell::ShaderStage stage) {
return TextureSamplerNames[static_cast<size_t>(stage)];
}

View File

@@ -102,6 +102,20 @@ void Config::ReadValues() {
qt_config->beginGroup("Data Storage");
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
FileUtil::GetUserPath(
FileUtil::UserPath::NANDDir,
qt_config
->value("nand_directory",
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)))
.toString()
.toStdString());
FileUtil::GetUserPath(
FileUtil::UserPath::SDMCDir,
qt_config
->value("sdmc_directory",
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
.toString()
.toStdString());
qt_config->endGroup();
qt_config->beginGroup("System");
@@ -222,6 +236,10 @@ void Config::SaveValues() {
qt_config->beginGroup("Data Storage");
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
qt_config->setValue("nand_directory",
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
qt_config->setValue("sdmc_directory",
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
qt_config->endGroup();
qt_config->beginGroup("System");

View File

@@ -13,7 +13,6 @@
#include <QKeyEvent>
#include <QMenu>
#include <QThreadPool>
#include <boost/container/flat_map.hpp>
#include <fmt/format.h>
#include "common/common_paths.h"
#include "common/common_types.h"
@@ -21,6 +20,8 @@
#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/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs_real.h"
@@ -232,6 +233,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");
@@ -432,7 +434,7 @@ void GameList::LoadInterfaceLayout() {
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
}
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"};
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));
@@ -454,6 +456,25 @@ static QString FormatGameName(const std::string& physical_name) {
return physical_name_as_qstring;
}
static 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;
}
void GameList::RefreshGameDirectory() {
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
@@ -462,26 +483,14 @@ void GameList::RefreshGameDirectory() {
}
}
static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
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;
}
}
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
if (icon_file != nullptr)
icon = icon_file->ReadAllBytes();
if (nacp != nullptr)
name = nacp->GetApplicationName();
}
GameListWorker::GameListWorker(
@@ -492,7 +501,8 @@ GameListWorker::GameListWorker(
GameListWorker::~GameListWorker() = default;
void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
void GameListWorker::AddInstalledTitlesToGameList() {
const auto cache = Service::FileSystem::GetUnionContents();
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
FileSys::ContentRecordType::Program);
@@ -507,14 +517,25 @@ void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::Regis
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(control, icon, name);
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()),
@@ -580,12 +601,14 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
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(nca, icon, name);
GetMetadataFromControlNCA(patch, nca, icon, name);
}
}
@@ -602,6 +625,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
program_id),
new GameListItemCompat(compatibility),
new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(FileUtil::GetSize(physical_name)),
@@ -621,9 +645,7 @@ 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());
AddInstalledTitlesToGameList();
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
nca_control_map.clear();
emit Finished(watch_list);

View File

@@ -38,6 +38,7 @@ public:
enum {
COLUMN_NAME,
COLUMN_COMPATIBILITY,
COLUMN_ADD_ONS,
COLUMN_FILE_TYPE,
COLUMN_SIZE,
COLUMN_COUNT, // Number of columns

View File

@@ -239,7 +239,7 @@ private:
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 AddInstalledTitlesToGameList();
void FillControlMap(const std::string& dir_path);
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
};

View File

@@ -12,6 +12,7 @@
#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QMessageBox>
#include <QtGui>
@@ -30,9 +31,14 @@
#include "core/core.h"
#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"
#include "core/file_sys/vfs_real.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
@@ -74,6 +80,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
*/
enum class CalloutFlag : uint32_t {
Telemetry = 0x1,
DRDDeprecation = 0x2,
};
static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
@@ -373,6 +380,10 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::OnMenuInstallToNAND);
connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
&GMainWindow::OnMenuSelectGameListRoot);
connect(ui.action_Select_NAND_Directory, &QAction::triggered, this,
[this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); });
connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
[this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
// Emulation
@@ -482,6 +493,23 @@ bool GMainWindow::LoadROM(const QString& filename) {
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
const auto drd_callout =
(UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0;
if (result == Core::System::ResultStatus::Success &&
system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory &&
drd_callout) {
UISettings::values.callout_flags |= static_cast<u32>(CalloutFlag::DRDDeprecation);
QMessageBox::warning(
this, tr("Warning Outdated Game Format"),
tr("You are using the deconstructed ROM directory format for this game, which is an "
"outdated format that has been superseded by others such as NCA, NAX, XCI, or "
"NSP. Deconstructed ROM directories lack icons, metadata, and update "
"support.<br><br>For an explanation of the various Switch formats yuzu supports, <a "
"href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our "
"wiki</a>. This message will not be shown again."));
}
render_window->DoneCurrent();
if (result != Core::System::ResultStatus::Success) {
@@ -566,8 +594,16 @@ 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,
@@ -740,7 +776,8 @@ void GMainWindow::OnMenuLoadFolder() {
void GMainWindow::OnMenuInstallToNAND() {
const QString file_filter =
tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive "
"(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge "
"Image (*.xci)");
QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
UISettings::values.roms_path, file_filter);
@@ -800,22 +837,34 @@ void GMainWindow::OnMenuInstallToNAND() {
QMessageBox::Yes;
};
if (filename.endsWith("xci", Qt::CaseInsensitive)) {
const auto xci = std::make_shared<FileSys::XCI>(
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
if (xci->GetStatus() != Loader::ResultStatus::Success) {
if (filename.endsWith("xci", Qt::CaseInsensitive) ||
filename.endsWith("nsp", Qt::CaseInsensitive)) {
std::shared_ptr<FileSys::NSP> nsp;
if (filename.endsWith("nsp", Qt::CaseInsensitive)) {
nsp = std::make_shared<FileSys::NSP>(
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
if (nsp->IsExtractedType())
failed();
} else {
const auto xci = std::make_shared<FileSys::XCI>(
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
nsp = xci->GetSecurePartitionNSP();
}
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
failed();
return;
}
const auto res =
Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy);
if (res == FileSys::InstallResult::Success) {
success();
} else {
if (res == FileSys::InstallResult::ErrorAlreadyExists) {
if (overwrite()) {
const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
xci, true, qt_raw_copy);
nsp, true, qt_raw_copy);
if (res2 == FileSys::InstallResult::Success) {
success();
} else {
@@ -829,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;
}
@@ -888,6 +941,28 @@ void GMainWindow::OnMenuSelectGameListRoot() {
}
}
void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) {
const auto res = QMessageBox::information(
this, tr("Changing Emulated Directory"),
tr("You are about to change the emulated %1 directory of the system. Please note "
"that this does not also move the contents of the previous directory to the "
"new one and you will have to do that yourself.")
.arg(target == EmulatedDirectoryTarget::SDMC ? tr("SD card") : tr("NAND")),
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
if (res == QMessageBox::Cancel)
return;
QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!dir_path.isEmpty()) {
FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
: FileUtil::UserPath::NANDDir,
dir_path.toStdString());
Service::FileSystem::CreateFactories(vfs);
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
}
void GMainWindow::OnMenuRecentFile() {
QAction* action = qobject_cast<QAction*>(sender());
assert(action);

View File

@@ -35,6 +35,11 @@ namespace Tegra {
class DebugContext;
}
enum class EmulatedDirectoryTarget {
NAND,
SDMC,
};
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -140,6 +145,8 @@ private slots:
void OnMenuInstallToNAND();
/// Called whenever a user selects the "File->Select Game List Root" menu item
void OnMenuSelectGameListRoot();
/// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card
void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
void OnMenuRecentFile();
void OnConfigure();
void OnAbout();

View File

@@ -65,6 +65,9 @@
<addaction name="action_Select_Game_List_Root"/>
<addaction name="menu_recent_files"/>
<addaction name="separator"/>
<addaction name="action_Select_NAND_Directory"/>
<addaction name="action_Select_SDMC_Directory"/>
<addaction name="separator"/>
<addaction name="action_Exit"/>
</widget>
<widget class="QMenu" name="menu_Emulation">
@@ -204,6 +207,22 @@
<string>Selects a folder to display in the game list</string>
</property>
</action>
<action name="action_Select_NAND_Directory">
<property name="text">
<string>Select NAND Directory...</string>
</property>
<property name="toolTip">
<string>Selects a folder to use as the root of the emulated NAND</string>
</property>
</action>
<action name="action_Select_SDMC_Directory">
<property name="text">
<string>Select SD Card Directory...</string>
</property>
<property name="toolTip">
<string>Selects a folder to use as the root of the emulated SD card</string>
</property>
</action>
<action name="action_Fullscreen">
<property name="checkable">
<bool>true</bool>

View File

@@ -114,6 +114,12 @@ void Config::ReadValues() {
// Data Storage
Settings::values.use_virtual_sd =
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
sdl2_config->Get("Data Storage", "nand_directory",
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
sdl2_config->Get("Data Storage", "nand_directory",
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
// System
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);