Compare commits

..

28 Commits

Author SHA1 Message Date
Zach Hilman 35e4a47be0 registration: Various style and documentation improvements
Fix logic in RealVfsFilesystem Create methods
Remove magic numbers
Fix regex errors
2018-08-12 15:55:44 -04:00
Zach Hilman 6b76b77400 registration: Add support for force overwrite of installed 2018-08-11 23:01:42 -04:00
Zach Hilman fdf27bf390 game_list: Split game list scans to multiple functions
Avoids unnecessary rebuilds of control data on every layer of recursion in AddFstEntriesToGameList
2018-08-11 22:50:48 -04:00
Zach Hilman 8f06a0f898 vfs_real: Add CreateFullPath to Create* operations 2018-08-11 22:50:48 -04:00
Zach Hilman dda8ef11c7 control_metadata: Remove unnecessary reference to base file 2018-08-11 22:50:48 -04:00
Zach Hilman 149bda980a romfs: Remove cyclic shared_ptr leak in romfs code 2018-08-11 22:50:48 -04:00
Zach Hilman 893447b6b0 registration: Update documentation and style 2018-08-11 22:50:48 -04:00
Zach Hilman 22bdddd6f0 nca_metadata: Remove unnecessary reference to base file 2018-08-11 22:50:48 -04:00
Zach Hilman 62e859c6c7 bis_factory: Create NAND dirs if they don't exist 2018-08-11 22:50:48 -04:00
Zach Hilman f78a6e752f qt: Use custom RawCopy with progress bar for installs 2018-08-11 22:50:48 -04:00
Zach Hilman 3b3c919e20 registration: Take RawCopy function as parameter
Instead of defaulting to VfsRawCopy
2018-08-11 22:50:48 -04:00
Zach Hilman 10812f8407 game_list: Populate control data from installed NAND 2018-08-11 22:50:48 -04:00
Zach Hilman e5504a060d registered_cache: Fix missing reading from yuzu_meta 2018-08-11 22:50:48 -04:00
Zach Hilman 167bfddafa file_sys: Comply to style guidelines 2018-08-11 22:50:48 -04:00
Zach Hilman bfb945c243 qt: Add 'Install to NAND' option to menu
Prompts for title type on NCA files.
2018-08-11 22:50:48 -04:00
Zach Hilman b67e751ccb game_list: Modify game list to scan installed titles 2018-08-11 22:50:48 -04:00
Zach Hilman a91983b11c file_sys: Add RegisteredCache
Manages NAND NCA get and install.
2018-08-11 22:50:48 -04:00
Zach Hilman 9aab787122 file_sys: Add support for parsing NCA metadata (CNMT) 2018-08-11 22:50:48 -04:00
Zach Hilman ab8acce645 card_image: Add accessor for all NCAs in XCI 2018-08-11 22:50:48 -04:00
Zach Hilman 9b0e3556ed vfs_real: Add CreateFullPath to CreateFile
Fixes bugs with calling CreateFile when the immediate directory does not exist.
2018-08-11 22:50:48 -04:00
Zach Hilman c0257cf52f filesystem: Add Open and Register functions for BISFactory 2018-08-11 22:50:48 -04:00
Zach Hilman 70a510bd8f bis_factory: Add partial implementation of BISFactory
Creates and stores RegisteredCaches for user and system NAND, as creation of a RegisteredCache is expensive.
2018-08-11 22:50:48 -04:00
Zach Hilman 95bb1067c1 loader: Join 0* files in directory if filename is 00
i.e. Load the concatenated 00+01 if 01 exists as well. Needed for split NAND NCAs.
2018-08-11 22:50:48 -04:00
Zach Hilman 5b4119fa7f loader: Recognize filename '00' as NCA
Needed to avoid mismatch filetype warnings on split NAND NCAs
2018-08-11 22:50:08 -04:00
Zach Hilman 42114e1df4 vfs: Add ConcatenatedVfsFile 2018-08-11 22:50:08 -04:00
Zach Hilman a27ec24c0f crypto: Remove hex utilities from key_manager
Move to hex_util.h in common
2018-08-11 22:50:08 -04:00
Zach Hilman b70a831608 file_util: Add getter for NAND registration directory 2018-08-11 22:50:08 -04:00
Zach Hilman 10aac376d1 common: Move hex string processing to separate file 2018-08-11 22:50:08 -04:00
73 changed files with 1883 additions and 681 deletions
+7 -7
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.7)
# CMake 3.6 required for FindBoost to define IMPORTED libs properly on unknown Boost versions
cmake_minimum_required(VERSION 3.6)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals)
@@ -187,8 +187,8 @@ find_package(Threads REQUIRED)
if (ENABLE_SDL2)
if (YUZU_USE_BUNDLED_SDL2)
# Detect toolchain and platform
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
set(SDL2_VER "SDL2-2.0.8")
if (MSVC14 AND ARCHITECTURE_x86_64)
set(SDL2_VER "SDL2-2.0.5")
else()
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
endif()
@@ -220,7 +220,7 @@ if (YUZU_USE_BUNDLED_UNICORN)
if (MSVC)
message(STATUS "unicorn not found, falling back to bundled")
# Detect toolchain and platform
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
if (MSVC14 AND ARCHITECTURE_x86_64)
set(UNICORN_VER "unicorn-yuzu")
else()
message(FATAL_ERROR "No bundled Unicorn binaries for your toolchain. Disable YUZU_USE_BUNDLED_UNICORN and provide your own.")
@@ -279,7 +279,7 @@ endif()
if (ENABLE_QT)
if (YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
if (MSVC14 AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.10.0-msvc2015_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
@@ -303,7 +303,7 @@ endif()
# ======================================
IF (APPLE)
find_library(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
FIND_LIBRARY(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
-12
View File
@@ -26,18 +26,6 @@ AudioRenderer::AudioRenderer(AudioRendererParameter params,
QueueMixedBuffer(2);
}
u32 AudioRenderer::GetSampleRate() const {
return worker_params.sample_rate;
}
u32 AudioRenderer::GetSampleCount() const {
return worker_params.sample_count;
}
u32 AudioRenderer::GetMixBufferCount() const {
return worker_params.mix_buffer_count;
}
std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
// Copy UpdateDataHeader struct
UpdateDataHeader config{};
+1 -4
View File
@@ -26,7 +26,7 @@ enum class PlayState : u8 {
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
u32_le mix_buffer_count;
u32_le unknown_8;
u32_le unknown_c;
u32_le voice_count;
u32_le sink_count;
@@ -160,9 +160,6 @@ public:
std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params);
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
u32 GetSampleRate() const;
u32 GetSampleCount() const;
u32 GetMixBufferCount() const;
private:
class VoiceState {
-6
View File
@@ -4,7 +4,6 @@
#include <algorithm>
#include <cstring>
#include <mutex>
#include "audio_core/cubeb_sink.h"
#include "audio_core/stream.h"
@@ -67,8 +66,6 @@ public:
return;
}
std::lock_guard lock{queue_mutex};
queue.reserve(queue.size() + samples.size() * GetNumChannels());
if (is_6_channel) {
@@ -97,7 +94,6 @@ private:
u32 num_channels{};
bool is_6_channel{};
std::mutex queue_mutex;
std::vector<s16> queue;
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
@@ -157,8 +153,6 @@ long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const v
return {};
}
std::lock_guard lock{impl->queue_mutex};
const size_t frames_to_write{
std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))};
+2
View File
@@ -40,6 +40,8 @@ add_library(common STATIC
file_util.cpp
file_util.h
hash.h
hex_util.cpp
hex_util.h
logging/backend.cpp
logging/backend.h
logging/filter.cpp
+6
View File
@@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {
#endif
}
std::string GetNANDRegistrationDir(bool system) {
if (system)
return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
}
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
}
+2
View File
@@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
std::string GetHactoolConfigurationPath();
std::string GetNANDRegistrationDir(bool system = false);
// Returns the path to where the sys file are
std::string GetSysDirectory();
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/hex_util.h"
u8 ToHexNibble(char c1) {
if (c1 >= 65 && c1 <= 70)
return c1 - 55;
if (c1 >= 97 && c1 <= 102)
return c1 - 87;
if (c1 >= 48 && c1 <= 57)
return c1 - 48;
throw std::logic_error("Invalid hex digit");
}
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
if (len != 32)
throw std::logic_error("Not of correct size.");
return HexStringToArray<16>(str);
}
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
if (len != 64)
throw std::logic_error("Not of correct size.");
return HexStringToArray<32>(str);
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <string>
#include <fmt/format.h>
#include "common/common_types.h"
u8 ToHexNibble(char c1);
template <size_t Size, bool le = false>
std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
if constexpr (le) {
for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
} else {
for (size_t i = 0; i < 2 * Size; i += 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
}
return out;
}
template <size_t Size>
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;
for (u8 c : array)
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
return out;
}
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
+5 -5
View File
@@ -16,7 +16,7 @@ struct ThreadQueueList {
// (dynamically resizable) circular buffers to remove their overhead when
// inserting and popping.
using Priority = unsigned int;
typedef unsigned int Priority;
// Number of priority levels. (Valid levels are [0..NUM_QUEUES).)
static const Priority NUM_QUEUES = N;
@@ -26,9 +26,9 @@ struct ThreadQueueList {
}
// Only for debugging, returns priority level.
Priority contains(const T& uid) const {
Priority contains(const T& uid) {
for (Priority i = 0; i < NUM_QUEUES; ++i) {
const Queue& cur = queues[i];
Queue& cur = queues[i];
if (std::find(cur.data.cbegin(), cur.data.cend(), uid) != cur.data.cend()) {
return i;
}
@@ -37,8 +37,8 @@ struct ThreadQueueList {
return -1;
}
T get_first() const {
const Queue* cur = first;
T get_first() {
Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
return cur->data.front();
+8
View File
@@ -20,6 +20,8 @@ add_library(core STATIC
crypto/key_manager.h
crypto/ctr_encryption_layer.cpp
crypto/ctr_encryption_layer.h
file_sys/bis_factory.cpp
file_sys/bis_factory.h
file_sys/card_image.cpp
file_sys/card_image.h
file_sys/content_archive.cpp
@@ -29,10 +31,14 @@ add_library(core STATIC
file_sys/directory.h
file_sys/errors.h
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
file_sys/partition_filesystem.cpp
file_sys/partition_filesystem.h
file_sys/program_metadata.cpp
file_sys/program_metadata.h
file_sys/registered_cache.cpp
file_sys/registered_cache.h
file_sys/romfs.cpp
file_sys/romfs.h
file_sys/romfs_factory.cpp
@@ -43,6 +49,8 @@ add_library(core STATIC
file_sys/sdmc_factory.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
file_sys/vfs_concat.h
file_sys/vfs_offset.cpp
file_sys/vfs_offset.h
file_sys/vfs_real.cpp
+4 -11
View File
@@ -86,16 +86,7 @@ public:
}
void AddTicks(u64 ticks) override {
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
CoreTiming::AddTicks(amortized_ticks);
CoreTiming::AddTicks(ticks - num_interpreted_instructions);
num_interpreted_instructions = 0;
}
u64 GetTicksRemaining() override {
@@ -243,7 +234,9 @@ void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
}
void ARM_Dynarmic::PrepareReschedule() {
jit->HaltExecution();
if (jit->IsExecuting()) {
jit->HaltExecution();
}
}
void ARM_Dynarmic::ClearInstructionCache() {
+34 -1
View File
@@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/gdbstub/gdbstub.h"
@@ -17,6 +18,7 @@
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/settings.h"
#include "file_sys/vfs_concat.h"
#include "file_sys/vfs_real.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
@@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {
return RunLoop(false);
}
static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path) {
// To account for split 00+01+etc files.
std::string dir_name;
std::string filename;
Common::SplitPath(path, &dir_name, &filename, nullptr);
if (filename == "00") {
const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
std::vector<FileSys::VirtualFile> concat;
for (u8 i = 0; i < 0x10; ++i) {
auto next = dir->GetFile(fmt::format("{:02X}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else {
next = dir->GetFile(fmt::format("{:02x}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else
break;
}
}
if (concat.empty())
return nullptr;
return FileSys::ConcatenateFiles(concat, dir->GetName());
}
return vfs->OpenFile(path, FileSys::Mode::Read);
}
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read));
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
-4
View File
@@ -14,7 +14,6 @@
#include "core/core_timing.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/settings.h"
namespace Core {
@@ -91,7 +90,6 @@ void Cpu::RunLoop(bool tight_loop) {
LOG_TRACE(Core, "Core-{} idling", core_index);
if (IsMainCore()) {
// TODO(Subv): Only let CoreTiming idle if all 4 cores are idling.
CoreTiming::Idle();
CoreTiming::Advance();
}
@@ -127,8 +125,6 @@ void Cpu::Reschedule() {
}
reschedule_pending = false;
// Lock the global kernel mutex when we manipulate the HLE state
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
scheduler->Reschedule();
}
+1 -1
View File
@@ -79,7 +79,7 @@ private:
std::shared_ptr<CpuBarrier> cpu_barrier;
std::shared_ptr<Kernel::Scheduler> scheduler;
std::atomic<bool> reschedule_pending = false;
bool reschedule_pending{};
size_t core_index;
};
+2
View File
@@ -135,9 +135,11 @@ void ClearPendingEvents() {
void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
ASSERT(event_type != nullptr);
s64 timeout = GetTicks() + cycles_into_future;
// If this event needs to be scheduled before the next advance(), force one early
if (!is_global_timer_sane)
ForceExceptionCheck(cycles_into_future);
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
+2 -33
View File
@@ -10,44 +10,13 @@
#include <string_view>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/key_manager.h"
#include "core/settings.h"
namespace Core::Crypto {
static u8 ToHexNibble(char c1) {
if (c1 >= 65 && c1 <= 70)
return c1 - 55;
if (c1 >= 97 && c1 <= 102)
return c1 - 87;
if (c1 >= 48 && c1 <= 57)
return c1 - 48;
throw std::logic_error("Invalid hex digit");
}
template <size_t Size>
static std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
for (size_t i = 0; i < 2 * Size; i += 2) {
auto d1 = str[i];
auto d2 = str[i + 1];
out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
}
return out;
}
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
if (len != 32)
throw std::logic_error("Not of correct size.");
return HexStringToArray<16>(str);
}
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
if (len != 64)
throw std::logic_error("Not of correct size.");
return HexStringToArray<32>(str);
}
KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
-3
View File
@@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {
namespace Core::Crypto {
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
class KeyManager {
public:
KeyManager();
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/file_sys/bis_factory.h"
namespace FileSys {
static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
const auto res = dir->GetDirectoryRelative(path);
if (res == nullptr)
return dir->CreateDirectoryRelative(path);
return res;
}
BISFactory::BISFactory(VirtualDir nand_root_)
: nand_root(std::move(nand_root_)),
sysnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
usrnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
return sysnand_cache;
}
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
return usrnand_cache;
}
} // namespace FileSys
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/loader/loader.h"
#include "registered_cache.h"
namespace FileSys {
/// 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);
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
private:
VirtualDir nand_root;
std::shared_ptr<RegisteredCache> sysnand_cache;
std::shared_ptr<RegisteredCache> usrnand_cache;
};
} // namespace FileSys
+8 -4
View File
@@ -93,6 +93,10 @@ VirtualDir XCI::GetLogoPartition() const {
return GetPartition(XCIPartition::Logo);
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
return ncas;
}
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
const auto iter =
std::find_if(ncas.begin(), ncas.end(),
@@ -107,19 +111,19 @@ VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
return nullptr;
}
std::vector<VirtualFile> XCI::GetFiles() const {
std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const {
return {};
}
std::vector<VirtualDir> XCI::GetSubdirectories() const {
return {};
std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const {
return std::vector<std::shared_ptr<VfsDirectory>>();
}
std::string XCI::GetName() const {
return file->GetName();
}
VirtualDir XCI::GetParentDirectory() const {
std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const {
return file->GetContainingDirectory();
}
+4 -3
View File
@@ -68,16 +68,17 @@ public:
VirtualDir GetUpdatePartition() const;
VirtualDir GetLogoPartition() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
VirtualFile GetNCAFileByType(NCAContentType type) const;
std::vector<VirtualFile> GetFiles() const override;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
std::string GetName() const override;
VirtualDir GetParentDirectory() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
+1 -1
View File
@@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
}
NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) {
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
file->ReadObject(raw.get());
}
-1
View File
@@ -81,7 +81,6 @@ public:
std::string GetVersionString() const;
private:
VirtualFile file;
std::unique_ptr<RawNACP> raw;
};
+131
View File
@@ -0,0 +1,131 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/common_funcs.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "content_archive.h"
#include "core/file_sys/nca_metadata.h"
namespace FileSys {
bool operator>=(TitleType lhs, TitleType rhs) {
return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs);
}
bool operator<=(TitleType lhs, TitleType rhs) {
return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs);
}
CNMT::CNMT(VirtualFile file) {
if (file->ReadObject(&header) != sizeof(CNMTHeader))
return;
// If type is {Application, Update, AOC} has opt-header.
if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
LOG_WARNING(Loader, "Failed to read optional header.");
}
}
for (u16 i = 0; i < header.number_content_entries; ++i) {
auto& next = content_records.emplace_back(ContentRecord{});
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
header.table_offset) != sizeof(ContentRecord)) {
content_records.erase(content_records.end() - 1);
}
}
for (u16 i = 0; i < header.number_meta_entries; ++i) {
auto& next = meta_records.emplace_back(MetaRecord{});
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
header.table_offset) != sizeof(MetaRecord)) {
meta_records.erase(meta_records.end() - 1);
}
}
}
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
std::vector<MetaRecord> meta_records)
: header(std::move(header)), opt_header(std::move(opt_header)),
content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
u64 CNMT::GetTitleID() const {
return header.title_id;
}
u32 CNMT::GetTitleVersion() const {
return header.title_version;
}
TitleType CNMT::GetType() const {
return header.type;
}
const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
return content_records;
}
const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
return meta_records;
}
bool CNMT::UnionRecords(const CNMT& other) {
bool change = false;
for (const auto& rec : other.content_records) {
const auto iter = std::find_if(content_records.begin(), content_records.end(),
[&rec](const ContentRecord& r) {
return r.nca_id == rec.nca_id && r.type == rec.type;
});
if (iter == content_records.end()) {
content_records.emplace_back(rec);
++header.number_content_entries;
change = true;
}
}
for (const auto& rec : other.meta_records) {
const auto iter =
std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
return r.title_id == rec.title_id && r.title_version == rec.title_version &&
r.type == rec.type;
});
if (iter == meta_records.end()) {
meta_records.emplace_back(rec);
++header.number_meta_entries;
change = true;
}
}
return change;
}
std::vector<u8> CNMT::Serialize() const {
const bool has_opt_header =
header.type >= TitleType::Application && header.type <= TitleType::AOC;
const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
std::vector<u8> out(
std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
memcpy(out.data(), &header, sizeof(CNMTHeader));
// Optional Header
if (has_opt_header) {
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
}
auto offset = header.table_offset;
for (const auto& rec : content_records) {
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
offset += sizeof(ContentRecord);
}
for (const auto& rec : meta_records) {
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
offset += sizeof(MetaRecord);
}
return out;
}
} // namespace FileSys
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstring>
#include <memory>
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
class CNMT;
struct CNMTHeader;
struct OptionalHeader;
enum class TitleType : u8 {
SystemProgram = 0x01,
SystemDataArchive = 0x02,
SystemUpdate = 0x03,
FirmwarePackageA = 0x04,
FirmwarePackageB = 0x05,
Application = 0x80,
Update = 0x81,
AOC = 0x82,
DeltaTitle = 0x83,
};
bool operator>=(TitleType lhs, TitleType rhs);
bool operator<=(TitleType lhs, TitleType rhs);
enum class ContentRecordType : u8 {
Meta = 0,
Program = 1,
Data = 2,
Control = 3,
Manual = 4,
Legal = 5,
Patch = 6,
};
struct ContentRecord {
std::array<u8, 0x20> hash;
std::array<u8, 0x10> nca_id;
std::array<u8, 0x6> size;
ContentRecordType type;
INSERT_PADDING_BYTES(1);
};
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
struct MetaRecord {
u64_le title_id;
u32_le title_version;
TitleType type;
u8 install_byte;
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size.");
struct OptionalHeader {
u64_le title_id;
u64_le minimum_version;
};
static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size.");
struct CNMTHeader {
u64_le title_id;
u32_le title_version;
TitleType type;
INSERT_PADDING_BYTES(1);
u16_le table_offset;
u16_le number_content_entries;
u16_le number_meta_entries;
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or
// meta0.ncd. These describe which NCA's belong with which titles in the registered cache.
class CNMT {
public:
explicit CNMT(VirtualFile file);
CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
std::vector<MetaRecord> meta_records);
u64 GetTitleID() const;
u32 GetTitleVersion() const;
TitleType GetType() const;
const std::vector<ContentRecord>& GetContentRecords() const;
const std::vector<MetaRecord>& GetMetaRecords() const;
bool UnionRecords(const CNMT& other);
std::vector<u8> Serialize() const;
private:
CNMTHeader header;
OptionalHeader opt_header;
std::vector<ContentRecord> content_records;
std::vector<MetaRecord> meta_records;
// TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data
// after the table. This is not documented, unfortunately.
};
} // namespace FileSys
+1 -1
View File
@@ -13,7 +13,7 @@
#include "core/file_sys/vfs.h"
namespace Loader {
enum class ResultStatus : u16;
enum class ResultStatus;
}
namespace FileSys {
+1 -1
View File
@@ -13,7 +13,7 @@
#include "partition_filesystem.h"
namespace Loader {
enum class ResultStatus : u16;
enum class ResultStatus;
}
namespace FileSys {
+476
View File
@@ -0,0 +1,476 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <regex>
#include <mbedtls/sha256.h>
#include "common/assert.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/encryption_layer.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_concat.h"
namespace FileSys {
std::string RegisteredCacheEntry::DebugInfo() const {
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
}
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
static bool FollowsTwoDigitDirFormat(std::string_view name) {
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
std::regex_constants::icase);
return std::regex_match(name.begin(), name.end(), two_digit_regex);
}
static bool FollowsNcaIdFormat(std::string_view name) {
static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
std::regex_constants::icase);
return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
}
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
bool within_two_digit) {
if (!within_two_digit)
return fmt::format("/{}.nca", HexArrayToString(nca_id, second_hex_upper));
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
return fmt::format("/000000{:02X}/{}.nca", hash[0], HexArrayToString(nca_id, second_hex_upper));
}
static std::string GetCNMTName(TitleType type, u64 title_id) {
constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{
"SystemProgram",
"SystemData",
"SystemUpdate",
"BootImagePackage",
"BootImagePackageSafe",
"Application",
"Patch",
"AddOnContent",
"" ///< Currently unknown 'DeltaTitle'
};
auto index = static_cast<size_t>(type);
// If the index is after the jump in TitleType, subtract it out.
if (index >= static_cast<size_t>(TitleType::Application)) {
index -= static_cast<size_t>(TitleType::Application) -
static_cast<size_t>(TitleType::FirmwarePackageB);
}
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
// TODO(DarkLordZach): Differentiate between Program and Patch
return ContentRecordType::Program;
case NCAContentType::Meta:
return ContentRecordType::Meta;
case NCAContentType::Control:
return ContentRecordType::Control;
case NCAContentType::Data:
return ContentRecordType::Data;
case NCAContentType::Manual:
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
return ContentRecordType::Manual;
default:
UNREACHABLE();
}
}
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
if (dir->GetFileRelative(path) != nullptr)
return dir->GetFileRelative(path);
if (dir->GetDirectoryRelative(path) != nullptr) {
const auto nca_dir = dir->GetDirectoryRelative(path);
VirtualFile file = nullptr;
const auto files = nca_dir->GetFiles();
if (files.size() == 1 && files[0]->GetName() == "00") {
file = files[0];
} else {
std::vector<VirtualFile> concat;
// Since the files are a two-digit hex number, max is FF.
for (size_t i = 0; i < 0x100; ++i) {
auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
if (next != nullptr) {
concat.push_back(std::move(next));
} else {
next = nca_dir->GetFile(fmt::format("{:02x}", i));
if (next != nullptr)
concat.push_back(std::move(next));
else
break;
}
}
if (concat.empty())
return nullptr;
file = FileSys::ConcatenateFiles(concat);
}
return file;
}
return nullptr;
}
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
VirtualFile file;
// Try all four modes of file storage:
// (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
// 00: /000000**/{:032X}.nca
// 01: /{:032X}.nca
// 10: /000000**/{:032x}.nca
// 11: /{:032x}.nca
for (u8 i = 0; i < 4; ++i) {
const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
file = OpenFileOrDirectoryConcat(dir, path);
if (file != nullptr)
return file;
}
return file;
}
static boost::optional<NcaID> CheckMapForContentRecord(
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
if (map.find(title_id) == map.end())
return boost::none;
const auto& cnmt = map.at(title_id);
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (iter == cnmt.GetContentRecords().end())
return boost::none;
return boost::make_optional(iter->nca_id);
}
boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
ContentRecordType type) const {
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
return meta_id.at(title_id);
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
if (res1 != boost::none)
return res1;
return CheckMapForContentRecord(meta, title_id, type);
}
std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
std::vector<NcaID> ids;
for (const auto& d2_dir : dir->GetSubdirectories()) {
if (FollowsNcaIdFormat(d2_dir->GetName())) {
ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20)));
continue;
}
if (!FollowsTwoDigitDirFormat(d2_dir->GetName()))
continue;
for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
if (!FollowsNcaIdFormat(nca_dir->GetName()))
continue;
ids.push_back(HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
}
for (const auto& nca_file : d2_dir->GetFiles()) {
if (!FollowsNcaIdFormat(nca_file->GetName()))
continue;
ids.push_back(HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
}
}
for (const auto& d2_file : dir->GetFiles()) {
if (FollowsNcaIdFormat(d2_file->GetName()))
ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20)));
}
return ids;
}
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
for (const auto& id : ids) {
const auto file = GetFileAtID(id);
if (file == nullptr)
continue;
const auto nca = std::make_shared<NCA>(parser(file, id));
if (nca->GetStatus() != Loader::ResultStatus::Success ||
nca->GetType() != NCAContentType::Meta) {
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& file : section0->GetFiles()) {
if (file->GetExtension() != "cnmt")
continue;
meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
meta_id.insert_or_assign(nca->GetTitleId(), id);
break;
}
}
}
void RegisteredCache::AccumulateYuzuMeta() {
const auto dir = this->dir->GetSubdirectory("yuzu_meta");
if (dir == nullptr)
return;
for (const auto& file : dir->GetFiles()) {
if (file->GetExtension() != "cnmt")
continue;
CNMT cnmt(file);
yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
}
}
void RegisteredCache::Refresh() {
if (dir == nullptr)
return;
const auto ids = AccumulateFiles();
ProcessFiles(ids);
AccumulateYuzuMeta();
}
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
Refresh();
}
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type) != nullptr;
}
bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry) != nullptr;
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
if (id == boost::none)
return nullptr;
return parser(GetFileAtID(id.get()), id.get());
}
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry.title_id, entry.type);
}
std::shared_ptr<NCA> RegisteredCache::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> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
template <typename T>
void RegisteredCache::IterateAllMetadata(
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
std::function<bool(const CNMT&, const ContentRecord&)> filter) const {
for (const auto& kv : meta) {
const auto& cnmt = kv.second;
if (filter(cnmt, EMPTY_META_CONTENT_RECORD))
out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD));
for (const auto& rec : cnmt.GetContentRecords()) {
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
out.push_back(proc(cnmt, rec));
}
}
}
for (const auto& kv : yuzu_meta) {
const auto& cnmt = kv.second;
for (const auto& rec : cnmt.GetContentRecords()) {
if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
out.push_back(proc(cnmt, rec));
}
}
}
}
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
std::vector<RegisteredCacheEntry> out;
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> RegisteredCache::ListEntriesFilter(
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
boost::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
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;
}
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
const auto filename = fmt::format("{}.nca", 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;
}
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
const auto& ncas = xci->GetNCAs();
const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
return nca->GetType() == NCAContentType::Meta;
});
if (meta_iter == ncas.end()) {
LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
"is therefore malformed. Double check your encryption keys.");
return InstallResult::ErrorMetaFailed;
}
// Install Metadata File
const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
const auto meta_id = HexStringToArray<16>(meta_id_raw);
const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
if (res != InstallResult::Success)
return res;
// Install all the other NCAs
const auto section0 = (*meta_iter)->GetSubdirectories()[0];
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);
if (nca == nullptr)
return InstallResult::ErrorCopyFailed;
const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
if (res2 != InstallResult::Success)
return res2;
}
Refresh();
return InstallResult::Success;
}
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
bool overwrite_if_exists, const VfsCopyFunction& copy) {
CNMTHeader header{
nca->GetTitleId(), ///< Title ID
0, ///< Ignore/Default title version
type, ///< Type
{}, ///< Padding
0x10, ///< Default table offset
1, ///< 1 Content Entry
0, ///< No Meta Entries
{}, ///< Padding
};
OptionalHeader opt_header{0, 0};
ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
memcpy(&c_rec.nca_id, &c_rec.hash, 16);
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
if (!RawInstallYuzuMeta(new_cnmt))
return InstallResult::ErrorMetaFailed;
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
boost::optional<NcaID> override_id) {
const auto in = nca->GetBaseFile();
Core::Crypto::SHA256Hash hash{};
// Calculate NcaID
// NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the
// game is massive), we're going to cheat and only hash the first MB of the NCA.
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
NcaID id{};
if (override_id == boost::none) {
const auto& data = in->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
memcpy(id.data(), hash.data(), 16);
} else {
id = override_id.get();
}
std::string path = GetRelativePathFromNcaID(id, false, true);
if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
return InstallResult::ErrorAlreadyExists;
}
if (GetFileAtID(id) != nullptr) {
LOG_WARNING(Loader, "Overwriting existing NCA...");
VirtualDir c_dir;
{ c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
c_dir->DeleteFile(FileUtil::GetFilename(path));
}
auto out = dir->CreateFileRelative(path);
if (out == nullptr)
return InstallResult::ErrorCopyFailed;
return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
}
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
// Reasoning behind this method can be found in the comment for InstallEntry, NCA overload.
const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta");
const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID());
if (dir->GetFile(filename) == nullptr) {
auto out = dir->CreateFile(filename);
const auto buffer = cnmt.Serialize();
out->Resize(buffer.size());
out->WriteBytes(buffer);
} else {
auto out = dir->GetFile(filename);
CNMT old_cnmt(out);
// Returns true on change
if (old_cnmt.UnionRecords(cnmt)) {
out->Resize(0);
const auto buffer = old_cnmt.Serialize();
out->Resize(buffer.size());
out->WriteBytes(buffer);
}
}
Refresh();
return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
[&cnmt](const std::pair<u64, CNMT>& kv) {
return kv.second.GetType() == cnmt.GetType() &&
kv.second.GetTitleID() == cnmt.GetTitleID();
}) != yuzu_meta.end();
}
} // namespace FileSys
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <map>
#include <memory>
#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;
using NcaID = std::array<u8, 0x10>;
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
enum class InstallResult {
Success,
ErrorAlreadyExists,
ErrorCopyFailed,
ErrorMetaFailed,
};
struct RegisteredCacheEntry {
u64 title_id;
ContentRecordType type;
std::string DebugInfo() const;
};
// boost flat_map requires operator< for O(log(n)) lookups.
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
/*
* A class that catalogues NCAs in the registered directory structure.
* Nintendo's registered format follows this structure:
*
* Root
* | 000000XX <- XX is the ____ two digits of the NcaID
* | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
* | 00
* | 01 <- Actual content split along 4GB boundaries. (optional)
*
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
* 4GB splitting can be ignored.)
*/
class RegisteredCache {
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
// parsing function.
explicit RegisteredCache(VirtualDir dir,
RegisteredCacheParsingFunction parsing_function =
[](const VirtualFile& file, const NcaID& id) { return file; });
void Refresh();
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(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;
// 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.
InstallResult InstallEntry(std::shared_ptr<XCI> xci, 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
// dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
// TODO(DarkLordZach): Author real meta-type NCAs and install those.
InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
private:
template <typename T>
void IterateAllMetadata(std::vector<T>& out,
std::function<T(const CNMT&, const ContentRecord&)> proc,
std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
std::vector<NcaID> AccumulateFiles() const;
void ProcessFiles(const std::vector<NcaID>& ids);
void AccumulateYuzuMeta();
boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
VirtualFile GetFileAtID(NcaID id) const;
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
boost::optional<NcaID> override_id = boost::none);
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
RegisteredCacheParsingFunction parser;
// maps tid -> NcaID of meta
boost::container::flat_map<u64, NcaID> meta_id;
// maps tid -> meta
boost::container::flat_map<u64, CNMT> meta;
// maps tid -> meta for CNMT in yuzu_meta
boost::container::flat_map<u64, CNMT> yuzu_meta;
};
} // namespace FileSys
+4 -4
View File
@@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t
auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
parent->AddFile(std::make_shared<OffsetVfsFile>(
file, entry.first.size, entry.first.offset + data_offset, entry.second, parent));
file, entry.first.size, entry.first.offset + data_offset, entry.second));
if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
break;
@@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s
while (true) {
auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
auto current = std::make_shared<VectorVfsDirectory>(
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second);
std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
@@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {
const u64 file_offset = header.file_meta.offset;
const u64 dir_offset = header.directory_meta.offset + 4;
const auto root =
auto root =
std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
file->GetContainingDirectory(), file->GetName());
file->GetName(), file->GetContainingDirectory());
ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
+10 -10
View File
@@ -74,15 +74,15 @@ VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view
return new_file;
}
VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
VirtualFile VfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
const auto old_path = FileUtil::SanitizePath(old_path_);
const auto new_path = FileUtil::SanitizePath(new_path_);
// Again, non-default impls are highly encouraged to provide a more optimized version of this.
auto out = CopyFile(sanitized_old_path, sanitized_new_path);
auto out = CopyFile(old_path_, new_path_);
if (out == nullptr)
return nullptr;
if (DeleteFile(sanitized_old_path))
if (DeleteFile(old_path))
return out;
return nullptr;
}
@@ -137,15 +137,15 @@ VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_
return new_dir;
}
VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
const auto sanitized_old_path = FileUtil::SanitizePath(old_path);
const auto sanitized_new_path = FileUtil::SanitizePath(new_path);
VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path_, std::string_view new_path_) {
const auto old_path = FileUtil::SanitizePath(old_path_);
const auto new_path = FileUtil::SanitizePath(new_path_);
// Non-default impls are highly encouraged to provide a more optimized version of this.
auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
auto out = CopyDirectory(old_path_, new_path_);
if (out == nullptr)
return nullptr;
if (DeleteDirectory(sanitized_old_path))
if (DeleteDirectory(old_path))
return out;
return nullptr;
}
+8 -12
View File
@@ -15,9 +15,9 @@
namespace FileSys {
class VfsDirectory;
class VfsFile;
class VfsFilesystem;
struct VfsFilesystem;
struct VfsFile;
struct VfsDirectory;
// Convenience typedefs to use Vfs* interfaces
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
@@ -34,9 +34,8 @@ enum class VfsEntryType {
// A class representing an abstract filesystem. A default implementation given the root VirtualDir
// is provided for convenience, but if the Vfs implementation has any additional state or
// functionality, they will need to override.
class VfsFilesystem : NonCopyable {
public:
explicit VfsFilesystem(VirtualDir root);
struct VfsFilesystem : NonCopyable {
VfsFilesystem(VirtualDir root);
virtual ~VfsFilesystem();
// Gets the friendly name for the filesystem.
@@ -82,8 +81,7 @@ protected:
};
// A class representing a file in an abstract filesystem.
class VfsFile : NonCopyable {
public:
struct VfsFile : NonCopyable {
virtual ~VfsFile();
// Retrieves the file name.
@@ -181,8 +179,7 @@ public:
};
// A class representing a directory in an abstract filesystem.
class VfsDirectory : NonCopyable {
public:
struct VfsDirectory : NonCopyable {
virtual ~VfsDirectory();
// Retrives the file located at path as if the current directory was root. Returns nullptr if
@@ -298,8 +295,7 @@ protected:
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
// if writable. This is to avoid redundant empty methods everywhere.
class ReadOnlyVfsDirectory : public VfsDirectory {
public:
struct ReadOnlyVfsDirectory : public VfsDirectory {
bool IsWritable() const override;
bool IsReadable() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <utility>
#include "core/file_sys/vfs_concat.h"
namespace FileSys {
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
if (files.empty())
return nullptr;
if (files.size() == 1)
return files[0];
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
: name(std::move(name)) {
size_t next_offset = 0;
for (const auto& file : files_) {
files[next_offset] = file;
next_offset += file->GetSize();
}
}
std::string ConcatenatedVfsFile::GetName() const {
if (files.empty())
return "";
if (!name.empty())
return name;
return files.begin()->second->GetName();
}
size_t ConcatenatedVfsFile::GetSize() const {
if (files.empty())
return 0;
return files.rbegin()->first + files.rbegin()->second->GetSize();
}
bool ConcatenatedVfsFile::Resize(size_t new_size) {
return false;
}
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
if (files.empty())
return nullptr;
return files.begin()->second->GetContainingDirectory();
}
bool ConcatenatedVfsFile::IsWritable() const {
return false;
}
bool ConcatenatedVfsFile::IsReadable() const {
return true;
}
size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const {
auto entry = files.end();
for (auto iter = files.begin(); iter != files.end(); ++iter) {
if (iter->first > offset) {
entry = --iter;
break;
}
}
// Check if the entry should be the last one. The loop above will make it end().
if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
--entry;
if (entry == files.end())
return 0;
const auto remaining = entry->second->GetSize() + offset - entry->first;
if (length > remaining) {
return entry->second->Read(data, remaining, offset - entry->first) +
Read(data + remaining, length - remaining, offset + remaining);
}
return entry->second->Read(data, length, offset - entry->first);
}
size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) {
return 0;
}
bool ConcatenatedVfsFile::Rename(std::string_view name) {
return false;
}
} // namespace FileSys
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string_view>
#include <boost/container/flat_map.hpp>
#include "core/file_sys/vfs.h"
namespace FileSys {
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
// read-only.
class ConcatenatedVfsFile : public VfsFile {
friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
public:
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 Read(u8* data, size_t length, size_t offset) const override;
size_t Write(const u8* data, size_t length, size_t offset) override;
bool Rename(std::string_view name) override;
private:
// Maps starting offset to file -- more efficient.
boost::container::flat_map<u64, VirtualFile> files;
std::string name;
};
} // namespace FileSys
+1 -2
View File
@@ -15,8 +15,7 @@ namespace FileSys {
// Similar to seeking to an offset.
// If the file is writable, operations that would write past the end of the offset file will expand
// the size of this wrapper.
class OffsetVfsFile : public VfsFile {
public:
struct OffsetVfsFile : public VfsFile {
OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0,
std::string new_name = "", VirtualDir new_parent = nullptr);
+14 -6
View File
@@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path))
return nullptr;
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
if (!FileUtil::Exists(path)) {
FileUtil::CreateFullPath(path_fwd);
if (!FileUtil::CreateEmptyFile(path))
return nullptr;
}
return OpenFile(path, perms);
}
@@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path))
return nullptr;
const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
if (!FileUtil::Exists(path)) {
FileUtil::CreateFullPath(path_fwd);
if (!FileUtil::CreateDir(path))
return nullptr;
}
// Cannot use make_shared as RealVfsDirectory constructor is private
return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
}
@@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FileUtil::Exists(full_path))
if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
return nullptr;
return base.OpenFile(full_path, perms);
}
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FileUtil::Exists(full_path))
if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
return nullptr;
return base.OpenDirectory(full_path, perms);
}
-1
View File
@@ -5,7 +5,6 @@
#pragma once
#include <string_view>
#include <boost/container/flat_map.hpp>
#include "common/file_util.h"
#include "core/file_sys/mode.h"
+2 -2
View File
@@ -8,8 +8,8 @@
namespace FileSys {
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
std::vector<VirtualDir> dirs_, VirtualDir parent_,
std::string name_)
std::vector<VirtualDir> dirs_, std::string name_,
VirtualDir parent_)
: files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
name(std::move(name_)) {}
+3 -4
View File
@@ -10,11 +10,10 @@ namespace FileSys {
// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
// Vector data is supplied upon construction.
class VectorVfsDirectory : public VfsDirectory {
public:
struct VectorVfsDirectory : public VfsDirectory {
explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr,
std::string name = "");
std::vector<VirtualDir> dirs = {}, std::string name = "",
VirtualDir parent = nullptr);
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+1 -1
View File
@@ -11,7 +11,7 @@
namespace Kernel {
std::atomic<u32> Object::next_object_id{0};
unsigned int Object::next_object_id;
/// Initialize the kernel
void Init() {
+7 -8
View File
@@ -4,7 +4,6 @@
#pragma once
#include <atomic>
#include <string>
#include <utility>
@@ -43,8 +42,8 @@ public:
virtual ~Object();
/// Returns a unique identifier for the object. For debugging purposes only.
u32 GetObjectId() const {
return object_id.load(std::memory_order_relaxed);
unsigned int GetObjectId() const {
return object_id;
}
virtual std::string GetTypeName() const {
@@ -62,23 +61,23 @@ public:
bool IsWaitable() const;
public:
static std::atomic<u32> next_object_id;
static unsigned int next_object_id;
private:
friend void intrusive_ptr_add_ref(Object*);
friend void intrusive_ptr_release(Object*);
std::atomic<u32> ref_count{0};
std::atomic<u32> object_id{next_object_id++};
unsigned int ref_count = 0;
unsigned int object_id = next_object_id++;
};
// Special functions used by boost::instrusive_ptr to do automatic ref-counting
inline void intrusive_ptr_add_ref(Object* object) {
object->ref_count.fetch_add(1, std::memory_order_relaxed);
++object->ref_count;
}
inline void intrusive_ptr_release(Object* object) {
if (object->ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
if (--object->ref_count == 0) {
delete object;
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ Scheduler::~Scheduler() {
}
}
bool Scheduler::HaveReadyThreads() const {
bool Scheduler::HaveReadyThreads() {
std::lock_guard<std::mutex> lock(scheduler_mutex);
return ready_queue.get_first() != nullptr;
}
+1 -1
View File
@@ -21,7 +21,7 @@ public:
~Scheduler();
/// Returns whether there are any threads that are ready to run.
bool HaveReadyThreads() const;
bool HaveReadyThreads();
/// Reschedules to the next available thread (call after current thread is suspended)
void Reschedule();
+3 -1
View File
@@ -532,6 +532,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread));
*out_handle = thread->guest_handle;
Core::System::GetInstance().PrepareReschedule();
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
LOG_TRACE(Kernel_SVC,
@@ -705,7 +706,8 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
auto owner = g_handle_table.Get<Thread>(owner_handle);
ASSERT(owner);
ASSERT(thread->status == ThreadStatus::WaitMutex);
ASSERT(thread->status != ThreadStatus::Running);
thread->status = ThreadStatus::WaitMutex;
thread->wakeup_callback = nullptr;
owner->AddMutexWaiter(thread);
+2 -30
View File
@@ -23,7 +23,6 @@
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -105,10 +104,6 @@ void ExitCurrentThread() {
*/
static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
const auto proper_handle = static_cast<Handle>(thread_handle);
// Lock the global kernel mutex when we enter the kernel HLE.
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>(proper_handle);
if (thread == nullptr) {
LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
@@ -160,10 +155,8 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
if (nanoseconds == -1)
return;
// This function might be called from any thread so we have to be cautious and use the
// thread-safe version of ScheduleEvent.
CoreTiming::ScheduleEventThreadsafe(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
callback_handle);
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
callback_handle);
}
void Thread::CancelWakeupTimer() {
@@ -426,33 +419,12 @@ VAddr Thread::GetCommandBufferAddress() const {
}
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
if (thread->lock_owner == this) {
// If the thread is already waiting for this thread to release the mutex, ensure that the
// waiters list is consistent and return without doing anything.
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
ASSERT(itr != wait_mutex_threads.end());
return;
}
// A thread can't wait on two different mutexes at the same time.
ASSERT(thread->lock_owner == nullptr);
// Ensure that the thread is not already in the list of mutex waiters
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
ASSERT(itr == wait_mutex_threads.end());
thread->lock_owner = this;
wait_mutex_threads.emplace_back(std::move(thread));
UpdatePriority();
}
void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
ASSERT(thread->lock_owner == this);
// Ensure that the thread is in the list of mutex waiters
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
ASSERT(itr != wait_mutex_threads.end());
boost::remove_erase(wait_mutex_threads, thread);
thread->lock_owner = nullptr;
UpdatePriority();
+6 -38
View File
@@ -20,9 +20,9 @@ public:
explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params)
: ServiceFramework("IAudioRenderer") {
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"},
{1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"},
{2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"},
{0, nullptr, "GetAudioRendererSampleRate"},
{1, nullptr, "GetAudioRendererSampleCount"},
{2, nullptr, "GetAudioRendererMixBufferCount"},
{3, nullptr, "GetAudioRendererState"},
{4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
{5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
@@ -45,27 +45,6 @@ private:
system_event->Signal();
}
void GetAudioRendererSampleRate(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleRate());
LOG_DEBUG(Service_Audio, "called");
}
void GetAudioRendererSampleCount(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleCount());
LOG_DEBUG(Service_Audio, "called");
}
void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetMixBufferCount());
LOG_DEBUG(Service_Audio, "called");
}
void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer()));
IPC::ResponseBuilder rb{ctx, 2};
@@ -190,8 +169,7 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") {
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
{2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
{3, nullptr, "OpenAudioRendererAuto"},
{4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo,
"GetAudioDeviceServiceWithRevisionInfo"},
{4, nullptr, "GetAudioDeviceServiceWithRevisionInfo"},
};
RegisterHandlers(functions);
}
@@ -211,7 +189,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
u64 buffer_sz = Common::AlignUp(4 * params.mix_buffer_count, 0x40);
u64 buffer_sz = Common::AlignUp(4 * params.unknown_8, 0x40);
buffer_sz += params.unknown_c * 1024;
buffer_sz += 0x940 * (params.unknown_c + 1);
buffer_sz += 0x3F0 * params.voice_count;
@@ -219,7 +197,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10);
buffer_sz +=
Common::AlignUp((0x3C0 * (params.sink_count + params.unknown_c) + 4 * params.sample_count) *
(params.mix_buffer_count + 6),
(params.unknown_8 + 6),
0x40);
if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
@@ -275,16 +253,6 @@ void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
}
void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<Audio::IAudioDevice>();
LOG_WARNING(Service_Audio, "(STUBBED) called"); // TODO(ogniK): Figure out what is different
// based on the current revision
}
bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const {
u32_be version_num = (revision - Common::MakeMagic('R', 'E', 'V', '0')); // Byte swap
switch (feature) {
-1
View File
@@ -22,7 +22,6 @@ private:
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
void GetAudioDevice(Kernel::HLERequestContext& ctx);
void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx);
enum class AudioFeatures : u32 {
Splitter,
@@ -226,6 +226,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
static std::unique_ptr<FileSys::BISFactory> bis_factory;
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
@@ -248,6 +249,13 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
return RESULT_SUCCESS;
}
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
bis_factory = std::move(factory);
LOG_DEBUG(Service_FS, "Registred BIS");
return RESULT_SUCCESS;
}
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
@@ -281,6 +289,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
return sdmc_factory->Open();
}
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
return bis_factory->GetSystemNANDContents();
}
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
return bis_factory->GetUserNANDContents();
}
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
romfs_factory = nullptr;
save_data_factory = nullptr;
@@ -291,6 +307,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
FileSys::Mode::ReadWrite);
if (bis_factory == nullptr)
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
save_data_factory = std::move(savedata);
+4 -4
View File
@@ -6,6 +6,7 @@
#include <memory>
#include "common/common_types.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/romfs_factory.h"
@@ -24,16 +25,15 @@ namespace FileSystem {
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
// TODO(DarkLordZach): BIS Filesystem
// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct);
ResultVal<FileSys::VirtualDir> OpenSDMC();
// TODO(DarkLordZach): BIS Filesystem
// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS();
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
/// Registers all Filesystem services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
+1 -8
View File
@@ -26,7 +26,7 @@ public:
{10600, nullptr, "DeclareOpenOnlinePlaySession"},
{10601, &IFriendService::DeclareCloseOnlinePlaySession,
"DeclareCloseOnlinePlaySession"},
{10610, &IFriendService::UpdateUserPresence, "UpdateUserPresence"},
{10610, nullptr, "UpdateUserPresence"},
{10700, nullptr, "GetPlayHistoryRegistrationKey"},
{10701, nullptr, "GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId"},
{10702, nullptr, "AddPlayHistory"},
@@ -99,13 +99,6 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void UpdateUserPresence(Kernel::HLERequestContext& ctx) {
// Stub used by Retro City Rampage
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
};
void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
+5 -12
View File
@@ -291,7 +291,6 @@ private:
class Hid final : public ServiceFramework<Hid> {
public:
Hid() : ServiceFramework("hid") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Hid::CreateAppletResource, "CreateAppletResource"},
{1, &Hid::ActivateDebugPad, "ActivateDebugPad"},
@@ -334,13 +333,15 @@ public:
{102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"},
{103, &Hid::ActivateNpad, "ActivateNpad"},
{104, nullptr, "DeactivateNpad"},
{106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"},
{107, &Hid::DisconnectNpad, "DisconnectNpad"},
{106, &Hid::AcquireNpadStyleSetUpdateEventHandle,
"AcquireNpadStyleSetUpdateEventHandle"},
{107, nullptr, "DisconnectNpad"},
{108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"},
{109, nullptr, "ActivateNpadWithRevision"},
{120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
{121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
{122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"},
{122, &Hid::SetNpadJoyAssignmentModeSingleByDefault,
"SetNpadJoyAssignmentModeSingleByDefault"},
{123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"},
{124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
{125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
@@ -397,8 +398,6 @@ public:
{1000, nullptr, "SetNpadCommunicationMode"},
{1001, nullptr, "GetNpadCommunicationMode"},
};
// clang-format on
RegisterHandlers(functions);
event = Kernel::Event::Create(Kernel::ResetType::OneShot, "hid:EventHandle");
@@ -497,12 +496,6 @@ private:
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void DisconnectNpad(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
+1 -1
View File
@@ -23,7 +23,7 @@ class HLERequestContext;
} // namespace Kernel
namespace FileSys {
class VfsFilesystem;
struct VfsFilesystem;
}
namespace Service {
+3 -1
View File
@@ -46,6 +46,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;
if (name == "00")
return FileType::NCA;
const std::string extension =
Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
@@ -126,7 +128,7 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{
};
std::string GetMessageForResultStatus(ResultStatus status) {
return GetMessageForResultStatus(static_cast<u16>(status));
return GetMessageForResultStatus(static_cast<size_t>(status));
}
std::string GetMessageForResultStatus(u16 status) {
+1 -1
View File
@@ -56,7 +56,7 @@ FileType GuessFromFilename(const std::string& name);
std::string GetFileTypeString(FileType type);
/// Return type for functions in Loader namespace
enum class ResultStatus : u16 {
enum class ResultStatus {
Success,
ErrorAlreadyLoaded,
ErrorNotImplemented,
+4 -25
View File
@@ -200,14 +200,6 @@ enum class IMinMaxExchange : u64 {
XHi = 3,
};
enum class XmadMode : u64 {
None = 0,
CLo = 1,
CHi = 2,
CSfu = 3,
CBcc = 4,
};
enum class FlowCondition : u64 {
Always = 0xF,
Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for?
@@ -464,18 +456,6 @@ union Instruction {
}
} bra;
union {
BitField<20, 16, u64> imm20_16;
BitField<36, 1, u64> product_shift_left;
BitField<37, 1, u64> merge_37;
BitField<48, 1, u64> sign_a;
BitField<49, 1, u64> sign_b;
BitField<50, 3, XmadMode> mode;
BitField<52, 1, u64> high_b;
BitField<53, 1, u64> high_a;
BitField<56, 1, u64> merge_56;
} xmad;
union {
BitField<20, 14, u64> offset;
BitField<34, 5, u64> index;
@@ -613,7 +593,6 @@ public:
IntegerSetPredicate,
PredicateSetPredicate,
Conversion,
Xmad,
Unknown,
};
@@ -803,10 +782,10 @@ private:
INST("010010110101----", Id::ISET_C, Type::IntegerSet, "ISET_C"),
INST("0011011-0101----", Id::ISET_IMM, Type::IntegerSet, "ISET_IMM"),
INST("0101000010010---", Id::PSETP, Type::PredicateSetPredicate, "PSETP"),
INST("0011011-00------", Id::XMAD_IMM, Type::Xmad, "XMAD_IMM"),
INST("0100111---------", Id::XMAD_CR, Type::Xmad, "XMAD_CR"),
INST("010100010-------", Id::XMAD_RC, Type::Xmad, "XMAD_RC"),
INST("0101101100------", Id::XMAD_RR, Type::Xmad, "XMAD_RR"),
INST("0011011-00------", Id::XMAD_IMM, Type::Arithmetic, "XMAD_IMM"),
INST("0100111---------", Id::XMAD_CR, Type::Arithmetic, "XMAD_CR"),
INST("010100010-------", Id::XMAD_RC, Type::Arithmetic, "XMAD_RC"),
INST("0101101100------", Id::XMAD_RR, Type::Arithmetic, "XMAD_RR"),
};
#undef INST
std::stable_sort(table.begin(), table.end(), [](const auto& a, const auto& b) {
-2
View File
@@ -46,7 +46,6 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
case RenderTargetFormat::RGBA32_FLOAT:
case RenderTargetFormat::RGBA32_UINT:
return 16;
case RenderTargetFormat::RGBA16_UINT:
case RenderTargetFormat::RGBA16_FLOAT:
case RenderTargetFormat::RG32_FLOAT:
return 8;
@@ -68,7 +67,6 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
case RenderTargetFormat::R16_UINT:
case RenderTargetFormat::R16_SINT:
case RenderTargetFormat::R16_FLOAT:
case RenderTargetFormat::RG8_UNORM:
case RenderTargetFormat::RG8_SNORM:
return 2;
case RenderTargetFormat::R8_UNORM:
-2
View File
@@ -20,7 +20,6 @@ enum class RenderTargetFormat : u32 {
NONE = 0x0,
RGBA32_FLOAT = 0xC0,
RGBA32_UINT = 0xC2,
RGBA16_UINT = 0xC9,
RGBA16_FLOAT = 0xCA,
RG32_FLOAT = 0xCB,
BGRA8_UNORM = 0xCF,
@@ -36,7 +35,6 @@ enum class RenderTargetFormat : u32 {
R11G11B10_FLOAT = 0xE0,
R32_FLOAT = 0xE5,
B5G6R5_UNORM = 0xE8,
RG8_UNORM = 0xEA,
RG8_SNORM = 0xEB,
R16_UNORM = 0xEE,
R16_SNORM = 0xEF,
+112 -80
View File
@@ -36,21 +36,30 @@ 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_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window)
: emu_window{window}, stream_buffer(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE) {
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window) : emu_window{window} {
// Create sampler objects
for (size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
state.texture_units[i].sampler = texture_samplers[i].sampler.handle;
}
// Create SSBOs
for (size_t stage = 0; stage < ssbos.size(); ++stage) {
for (size_t buffer = 0; buffer < ssbos[stage].size(); ++buffer) {
ssbos[stage][buffer].Create();
state.draw.const_buffers[stage][buffer].ssbo = ssbos[stage][buffer].handle;
}
}
GLint ext_num;
glGetIntegerv(GL_NUM_EXTENSIONS, &ext_num);
for (GLint i = 0; i < ext_num; i++) {
const std::string_view extension{
reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i))};
if (extension == "GL_ARB_direct_state_access") {
if (extension == "GL_ARB_buffer_storage") {
has_ARB_buffer_storage = true;
} else if (extension == "GL_ARB_direct_state_access") {
has_ARB_direct_state_access = true;
} else if (extension == "GL_ARB_separate_shader_objects") {
has_ARB_separate_shader_objects = true;
@@ -77,31 +86,47 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window)
hw_vao.Create();
state.draw.vertex_buffer = stream_buffer.GetHandle();
stream_buffer = OGLStreamBuffer::MakeBuffer(has_ARB_buffer_storage, GL_ARRAY_BUFFER);
stream_buffer->Create(STREAM_BUFFER_SIZE, STREAM_BUFFER_SIZE / 2);
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());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stream_buffer->GetHandle());
for (unsigned index = 0; index < uniform_buffers.size(); ++index) {
auto& buffer = uniform_buffers[index];
buffer.Create();
glBindBuffer(GL_UNIFORM_BUFFER, buffer.handle);
glBufferData(GL_UNIFORM_BUFFER, sizeof(GLShader::MaxwellUniformData), nullptr,
GL_STREAM_COPY);
glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer.handle);
}
glEnable(GL_BLEND);
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
}
RasterizerOpenGL::~RasterizerOpenGL() {}
RasterizerOpenGL::~RasterizerOpenGL() {
if (stream_buffer != nullptr) {
state.draw.vertex_buffer = stream_buffer->GetHandle();
state.Apply();
stream_buffer->Release();
}
}
std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
GLintptr buffer_offset) {
MICROPROFILE_SCOPE(OpenGL_VAO);
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
const auto& memory_manager = Core::System::GetInstance().GPU().memory_manager;
state.draw.vertex_array = hw_vao.handle;
state.draw.vertex_buffer = stream_buffer.GetHandle();
state.draw.vertex_buffer = stream_buffer->GetHandle();
state.Apply();
// Upload all guest vertex arrays sequentially to our buffer
@@ -116,15 +141,16 @@ 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);
// Copy vertex array data
Memory::ReadBlock(*memory_manager->GpuToCpuAddress(start), array_ptr, size);
// Bind the vertex array to the buffer at the current offset.
glBindVertexBuffer(index, stream_buffer.GetHandle(), vertex_buffer_offset,
vertex_array.stride);
glBindVertexBuffer(index, stream_buffer->GetHandle(), buffer_offset, vertex_array.stride);
ASSERT_MSG(vertex_array.divisor == 0, "Vertex buffer divisor unimplemented");
array_ptr += size;
buffer_offset += size;
}
// Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
@@ -175,12 +201,22 @@ static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program
return program_code;
}
std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
// Helper function for uploading uniform data
const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) {
if (has_ARB_direct_state_access) {
glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size);
} else {
glBindBuffer(GL_COPY_WRITE_BUFFER, handle);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size);
}
};
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Next available bindpoints to use when uploading the const buffers and textures to the GLSL
// shaders. The constbuffer bindpoint starts after the shader stage configuration bind points.
u32 current_constbuffer_bindpoint = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage;
u32 current_constbuffer_bindpoint = static_cast<u32>(uniform_buffers.size());
u32 current_texture_bindpoint = 0;
for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
@@ -192,21 +228,22 @@ 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));
// Bind the buffer
glBindBufferRange(GL_UNIFORM_BUFFER, stage, stream_buffer.GetHandle(), buffer_offset,
sizeof(ubo));
// Flush the buffer so that the GPU can see the data we just wrote.
glFlushMappedBufferRange(GL_ARRAY_BUFFER, buffer_offset, sizeof(ubo));
buffer_ptr += sizeof(ubo);
buffer_offset += sizeof(ubo);
// Upload uniform data as one UBO per stage
const GLintptr ubo_offset = buffer_offset;
copy_buffer(uniform_buffers[stage].handle, ubo_offset,
sizeof(GLShader::MaxwellUniformData));
buffer_ptr += sizeof(GLShader::MaxwellUniformData);
buffer_offset += sizeof(GLShader::MaxwellUniformData);
GLShader::ShaderSetup setup{GetShaderProgramCode(program)};
GLShader::ShaderEntries shader_resources;
@@ -245,9 +282,9 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr
static_cast<Maxwell::ShaderStage>(stage));
// 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), gl_stage_program,
current_constbuffer_bindpoint, shader_resources.const_buffer_entries);
current_constbuffer_bindpoint =
SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program,
current_constbuffer_bindpoint, shader_resources.const_buffer_entries);
// Configure the textures for this shader stage.
current_texture_bindpoint =
@@ -262,8 +299,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 {
@@ -397,31 +432,6 @@ void RasterizerOpenGL::Clear() {
}
}
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;
const auto& memory_manager = Core::System::GetInstance().GPU().memory_manager;
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;
@@ -446,7 +456,7 @@ void RasterizerOpenGL::DrawArrays() {
const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
const unsigned vertex_num{is_indexed ? regs.index_array.count : regs.vertex_buffer.count};
state.draw.vertex_buffer = stream_buffer.GetHandle();
state.draw.vertex_buffer = stream_buffer->GetHandle();
state.Apply();
size_t buffer_size = CalculateVertexArraysSize();
@@ -456,31 +466,41 @@ void RasterizerOpenGL::DrawArrays() {
}
// Uniform space for the 5 shader stages
buffer_size =
Common::AlignUp<size_t>(buffer_size, 4) +
(sizeof(GLShader::MaxwellUniformData) + uniform_buffer_alignment) * Maxwell::MaxShaderStage;
// Add space for at least 18 constant buffers
buffer_size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + uniform_buffer_alignment);
buffer_size = Common::AlignUp<size_t>(buffer_size, 4) +
sizeof(GLShader::MaxwellUniformData) * Maxwell::MaxShaderStage;
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;
std::tie(buffer_ptr, buffer_offset) =
stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4);
std::tie(buffer_ptr, buffer_offset) = SetupVertexArrays(buffer_ptr, buffer_offset);
u8* offseted_buffer;
std::tie(offseted_buffer, buffer_offset) = SetupVertexArrays(buffer_ptr, buffer_offset);
offseted_buffer =
reinterpret_cast<u8*>(Common::AlignUp(reinterpret_cast<size_t>(offseted_buffer), 4));
buffer_offset = Common::AlignUp<size_t>(buffer_offset, 4);
// 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);
const auto& memory_manager = Core::System::GetInstance().GPU().memory_manager;
const boost::optional<VAddr> index_data_addr{
memory_manager->GpuToCpuAddress(regs.index_array.StartAddress())};
Memory::ReadBlock(*index_data_addr, offseted_buffer, index_buffer_size);
index_buffer_offset = buffer_offset;
offseted_buffer += index_buffer_size;
buffer_offset += index_buffer_size;
}
std::tie(buffer_ptr, buffer_offset) = SetupShaders(buffer_ptr, buffer_offset);
offseted_buffer =
reinterpret_cast<u8*>(Common::AlignUp(reinterpret_cast<size_t>(offseted_buffer), 4));
buffer_offset = Common::AlignUp<size_t>(buffer_offset, 4);
stream_buffer.Unmap(buffer_ptr - buffer_ptr_base);
SetupShaders(offseted_buffer, buffer_offset);
stream_buffer->Unmap();
shader_program_manager->ApplyTo(state);
state.Apply();
@@ -627,23 +647,36 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
}
}
std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(
u8* buffer_ptr, GLintptr buffer_offset, Maxwell::ShaderStage stage, GLuint program,
u32 current_bindpoint, const std::vector<GLShader::ConstBufferEntry>& entries) {
u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, GLuint program,
u32 current_bindpoint,
const std::vector<GLShader::ConstBufferEntry>& entries) {
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
// Reset all buffer draw state for this stage.
for (auto& buffer : state.draw.const_buffers[static_cast<size_t>(stage)]) {
buffer.bindpoint = 0;
buffer.enabled = false;
}
// Upload only the enabled buffers from the 16 constbuffers of each shader stage
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)];
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
const auto& used_buffer = entries[bindpoint];
const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()];
auto& buffer_draw_state =
state.draw.const_buffers[static_cast<size_t>(stage)][used_buffer.GetIndex()];
if (!buffer.enabled) {
continue;
}
buffer_draw_state.enabled = true;
buffer_draw_state.bindpoint = current_bindpoint + bindpoint;
boost::optional<VAddr> addr = gpu.memory_manager->GpuToCpuAddress(buffer.address);
size_t size = 0;
if (used_buffer.IsIndirect()) {
@@ -665,26 +698,25 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(
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));
std::vector<u8> data(size);
Memory::ReadBlock(*addr, data.data(), data.size());
glBindBufferRange(GL_UNIFORM_BUFFER, current_bindpoint + bindpoint,
stream_buffer.GetHandle(), const_buffer_offset, size);
glBindBuffer(GL_UNIFORM_BUFFER, buffer_draw_state.ssbo);
glBufferData(GL_UNIFORM_BUFFER, data.size(), data.data(), GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// Now configure the bindpoint of the buffer inside the shader
const std::string buffer_name = used_buffer.GetName();
const GLuint index =
glGetProgramResourceIndex(program, GL_UNIFORM_BLOCK, buffer_name.c_str());
if (index != GL_INVALID_INDEX) {
glUniformBlockBinding(program, index, current_bindpoint + bindpoint);
glUniformBlockBinding(program, index, buffer_draw_state.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, GLuint program, u32 current_unit,
+10 -13
View File
@@ -7,7 +7,6 @@
#include <array>
#include <cstddef>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include <glad/glad.h>
@@ -101,10 +100,9 @@ private:
* @param entries Vector describing the buffers that are actually used in the guest shader.
* @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,
GLuint program, u32 current_bindpoint,
const std::vector<GLShader::ConstBufferEntry>& entries);
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, GLuint program,
u32 current_bindpoint,
const std::vector<GLShader::ConstBufferEntry>& entries);
/*
* Configures the current textures to use for the draw command.
@@ -141,6 +139,7 @@ private:
/// Syncs the blend state to match the guest state
void SyncBlendState();
bool has_ARB_buffer_storage = false;
bool has_ARB_direct_state_access = false;
bool has_ARB_separate_shader_objects = false;
bool has_ARB_vertex_attrib_binding = false;
@@ -156,24 +155,22 @@ private:
OGLVertexArray hw_vao;
std::array<SamplerInfo, GLShader::NumTextureSamplers> texture_samplers;
std::array<std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers>,
Tegra::Engines::Maxwell3D::Regs::MaxShaderStage>
ssbos;
static constexpr size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
OGLStreamBuffer stream_buffer;
std::unique_ptr<OGLStreamBuffer> stream_buffer;
OGLBuffer uniform_buffer;
OGLFramebuffer framebuffer;
GLint uniform_buffer_alignment;
size_t CalculateVertexArraysSize() const;
std::pair<u8*, GLintptr> SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset);
std::pair<u8*, GLintptr> SetupShaders(u8* buffer_ptr, GLintptr buffer_offset);
std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::MaxShaderStage> uniform_buffers;
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(u8* buffer_ptr, GLintptr buffer_offset);
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;
@@ -101,7 +101,6 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8
{GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F
{GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,
false}, // R11FG11FB10F
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI
@@ -135,7 +134,6 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S
{GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // SRGBA8
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
// DepthStencil formats
@@ -236,57 +234,32 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_bu
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
SurfaceParams::MaxPixelFormat>
morton_to_gl_fns = {
// clang-format off
MortonCopy<true, PixelFormat::ABGR8U>,
MortonCopy<true, PixelFormat::ABGR8S>,
MortonCopy<true, PixelFormat::B5G6R5>,
MortonCopy<true, PixelFormat::A2B10G10R10>,
MortonCopy<true, PixelFormat::A1B5G5R5>,
MortonCopy<true, PixelFormat::R8>,
MortonCopy<true, PixelFormat::R8UI>,
MortonCopy<true, PixelFormat::RGBA16F>,
MortonCopy<true, PixelFormat::RGBA16UI>,
MortonCopy<true, PixelFormat::R11FG11FB10F>,
MortonCopy<true, PixelFormat::RGBA32UI>,
MortonCopy<true, PixelFormat::DXT1>,
MortonCopy<true, PixelFormat::DXT23>,
MortonCopy<true, PixelFormat::DXT45>,
MortonCopy<true, PixelFormat::DXN1>,
MortonCopy<true, PixelFormat::DXN2UNORM>,
MortonCopy<true, PixelFormat::DXN2SNORM>,
MortonCopy<true, PixelFormat::BC7U>,
MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
MortonCopy<true, PixelFormat::G8R8>,
MortonCopy<true, PixelFormat::BGRA8>,
MortonCopy<true, PixelFormat::RGBA32F>,
MortonCopy<true, PixelFormat::RG32F>,
MortonCopy<true, PixelFormat::R32F>,
MortonCopy<true, PixelFormat::R16F>,
MortonCopy<true, PixelFormat::R16UNORM>,
MortonCopy<true, PixelFormat::R16S>,
MortonCopy<true, PixelFormat::R16UI>,
MortonCopy<true, PixelFormat::R16I>,
MortonCopy<true, PixelFormat::RG16>,
MortonCopy<true, PixelFormat::RG16F>,
MortonCopy<true, PixelFormat::RG16UI>,
MortonCopy<true, PixelFormat::RG16I>,
MortonCopy<true, PixelFormat::RG16S>,
MortonCopy<true, PixelFormat::RGB32F>,
MortonCopy<true, PixelFormat::SRGBA8>,
MortonCopy<true, PixelFormat::RG8U>,
MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::Z24S8>,
MortonCopy<true, PixelFormat::S8Z24>,
MortonCopy<true, PixelFormat::Z32F>,
MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::ABGR8U>, MortonCopy<true, PixelFormat::ABGR8S>,
MortonCopy<true, PixelFormat::B5G6R5>, MortonCopy<true, PixelFormat::A2B10G10R10>,
MortonCopy<true, PixelFormat::A1B5G5R5>, MortonCopy<true, PixelFormat::R8>,
MortonCopy<true, PixelFormat::R8UI>, MortonCopy<true, PixelFormat::RGBA16F>,
MortonCopy<true, PixelFormat::R11FG11FB10F>, MortonCopy<true, PixelFormat::RGBA32UI>,
MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>,
MortonCopy<true, PixelFormat::DXT45>, MortonCopy<true, PixelFormat::DXN1>,
MortonCopy<true, PixelFormat::DXN2UNORM>, MortonCopy<true, PixelFormat::DXN2SNORM>,
MortonCopy<true, PixelFormat::BC7U>, MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
MortonCopy<true, PixelFormat::G8R8>, MortonCopy<true, PixelFormat::BGRA8>,
MortonCopy<true, PixelFormat::RGBA32F>, MortonCopy<true, PixelFormat::RG32F>,
MortonCopy<true, PixelFormat::R32F>, MortonCopy<true, PixelFormat::R16F>,
MortonCopy<true, PixelFormat::R16UNORM>, MortonCopy<true, PixelFormat::R16S>,
MortonCopy<true, PixelFormat::R16UI>, MortonCopy<true, PixelFormat::R16I>,
MortonCopy<true, PixelFormat::RG16>, MortonCopy<true, PixelFormat::RG16F>,
MortonCopy<true, PixelFormat::RG16UI>, MortonCopy<true, PixelFormat::RG16I>,
MortonCopy<true, PixelFormat::RG16S>, MortonCopy<true, PixelFormat::RGB32F>,
MortonCopy<true, PixelFormat::SRGBA8>, MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::Z24S8>, MortonCopy<true, PixelFormat::S8Z24>,
MortonCopy<true, PixelFormat::Z32F>, MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::Z32FS8>,
// clang-format on
};
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
SurfaceParams::MaxPixelFormat>
gl_to_morton_fns = {
// clang-format off
MortonCopy<false, PixelFormat::ABGR8U>,
MortonCopy<false, PixelFormat::ABGR8S>,
MortonCopy<false, PixelFormat::B5G6R5>,
@@ -295,7 +268,6 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
MortonCopy<false, PixelFormat::R8>,
MortonCopy<false, PixelFormat::R8UI>,
MortonCopy<false, PixelFormat::RGBA16F>,
MortonCopy<false, PixelFormat::RGBA16UI>,
MortonCopy<false, PixelFormat::R11FG11FB10F>,
MortonCopy<false, PixelFormat::RGBA32UI>,
// TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/ASTC_2D_4X4 formats is not
@@ -325,14 +297,12 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
MortonCopy<false, PixelFormat::RG16S>,
MortonCopy<false, PixelFormat::RGB32F>,
MortonCopy<false, PixelFormat::SRGBA8>,
MortonCopy<false, PixelFormat::RG8U>,
MortonCopy<false, PixelFormat::RG8S>,
MortonCopy<false, PixelFormat::Z24S8>,
MortonCopy<false, PixelFormat::S8Z24>,
MortonCopy<false, PixelFormat::Z32F>,
MortonCopy<false, PixelFormat::Z16>,
MortonCopy<false, PixelFormat::Z32FS8>,
// clang-format on
};
// Allocate an uninitialized texture of appropriate size and format for the surface
@@ -31,45 +31,43 @@ struct SurfaceParams {
R8 = 5,
R8UI = 6,
RGBA16F = 7,
RGBA16UI = 8,
R11FG11FB10F = 9,
RGBA32UI = 10,
DXT1 = 11,
DXT23 = 12,
DXT45 = 13,
DXN1 = 14, // This is also known as BC4
DXN2UNORM = 15,
DXN2SNORM = 16,
BC7U = 17,
ASTC_2D_4X4 = 18,
G8R8 = 19,
BGRA8 = 20,
RGBA32F = 21,
RG32F = 22,
R32F = 23,
R16F = 24,
R16UNORM = 25,
R16S = 26,
R16UI = 27,
R16I = 28,
RG16 = 29,
RG16F = 30,
RG16UI = 31,
RG16I = 32,
RG16S = 33,
RGB32F = 34,
SRGBA8 = 35,
RG8U = 36,
RG8S = 37,
R11FG11FB10F = 8,
RGBA32UI = 9,
DXT1 = 10,
DXT23 = 11,
DXT45 = 12,
DXN1 = 13, // This is also known as BC4
DXN2UNORM = 14,
DXN2SNORM = 15,
BC7U = 16,
ASTC_2D_4X4 = 17,
G8R8 = 18,
BGRA8 = 19,
RGBA32F = 20,
RG32F = 21,
R32F = 22,
R16F = 23,
R16UNORM = 24,
R16S = 25,
R16UI = 26,
R16I = 27,
RG16 = 28,
RG16F = 29,
RG16UI = 30,
RG16I = 31,
RG16S = 32,
RGB32F = 33,
SRGBA8 = 34,
RG8S = 35,
MaxColorFormat,
// DepthStencil formats
Z24S8 = 38,
S8Z24 = 39,
Z32F = 40,
Z16 = 41,
Z32FS8 = 42,
Z24S8 = 36,
S8Z24 = 37,
Z32F = 38,
Z16 = 39,
Z32FS8 = 40,
MaxDepthStencilFormat,
@@ -115,7 +113,6 @@ struct SurfaceParams {
1, // R8
1, // R8UI
1, // RGBA16F
1, // RGBA16UI
1, // R11FG11FB10F
1, // RGBA32UI
4, // DXT1
@@ -143,7 +140,6 @@ struct SurfaceParams {
1, // RG16S
1, // RGB32F
1, // SRGBA8
1, // RG8U
1, // RG8S
1, // Z24S8
1, // S8Z24
@@ -169,7 +165,6 @@ struct SurfaceParams {
8, // R8
8, // R8UI
64, // RGBA16F
64, // RGBA16UI
32, // R11FG11FB10F
128, // RGBA32UI
64, // DXT1
@@ -197,7 +192,6 @@ struct SurfaceParams {
32, // RG16S
96, // RGB32F
32, // SRGBA8
16, // RG8U
16, // RG8S
32, // Z24S8
32, // S8Z24
@@ -247,8 +241,6 @@ struct SurfaceParams {
return PixelFormat::A2B10G10R10;
case Tegra::RenderTargetFormat::RGBA16_FLOAT:
return PixelFormat::RGBA16F;
case Tegra::RenderTargetFormat::RGBA16_UINT:
return PixelFormat::RGBA16UI;
case Tegra::RenderTargetFormat::RGBA32_FLOAT:
return PixelFormat::RGBA32F;
case Tegra::RenderTargetFormat::RG32_FLOAT:
@@ -273,8 +265,6 @@ struct SurfaceParams {
return PixelFormat::RG16;
case Tegra::RenderTargetFormat::RG16_SNORM:
return PixelFormat::RG16S;
case Tegra::RenderTargetFormat::RG8_UNORM:
return PixelFormat::RG8U;
case Tegra::RenderTargetFormat::RG8_SNORM:
return PixelFormat::RG8S;
case Tegra::RenderTargetFormat::R16_FLOAT:
@@ -442,7 +432,6 @@ struct SurfaceParams {
case Tegra::RenderTargetFormat::RG16_UNORM:
case Tegra::RenderTargetFormat::R16_UNORM:
case Tegra::RenderTargetFormat::B5G6R5_UNORM:
case Tegra::RenderTargetFormat::RG8_UNORM:
return ComponentType::UNorm;
case Tegra::RenderTargetFormat::RGBA8_SNORM:
case Tegra::RenderTargetFormat::RG16_SNORM:
@@ -458,7 +447,6 @@ struct SurfaceParams {
case Tegra::RenderTargetFormat::R32_FLOAT:
return ComponentType::Float;
case Tegra::RenderTargetFormat::RGBA32_UINT:
case Tegra::RenderTargetFormat::RGBA16_UINT:
case Tegra::RenderTargetFormat::RG16_UINT:
case Tegra::RenderTargetFormat::R8_UINT:
case Tegra::RenderTargetFormat::R16_UINT:
@@ -356,13 +356,13 @@ public:
* @param reg The register to use as the source value.
*/
void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
std::string dest = GetOutputAttribute(attribute);
std::string dest = GetOutputAttribute(attribute) + GetSwizzle(elem);
std::string src = GetRegisterAsFloat(reg);
if (!dest.empty()) {
// Can happen with unknown/unimplemented output attributes, in which case we ignore the
// instruction for now.
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
shader.AddLine(dest + " = " + src + ';');
}
}
@@ -376,8 +376,6 @@ public:
return value;
} else if (type == GLSLRegister::Type::Integer) {
return "floatBitsToInt(" + value + ')';
} else if (type == GLSLRegister::Type::UnsignedInteger) {
return "floatBitsToUint(" + value + ')';
} else {
UNREACHABLE();
}
@@ -1632,99 +1630,6 @@ private:
}
break;
}
case OpCode::Type::Xmad: {
ASSERT_MSG(!instr.xmad.sign_a, "Unimplemented");
ASSERT_MSG(!instr.xmad.sign_b, "Unimplemented");
std::string op_a{regs.GetRegisterAsInteger(instr.gpr8, 0, instr.xmad.sign_a)};
std::string op_b;
std::string op_c;
// TODO(bunnei): Needs to be fixed once op_a or op_b is signed
ASSERT_MSG(instr.xmad.sign_a == instr.xmad.sign_b, "Unimplemented");
const bool is_signed{instr.xmad.sign_a == 1};
bool is_merge{};
switch (opcode->GetId()) {
case OpCode::Id::XMAD_CR: {
is_merge = instr.xmad.merge_56;
op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
instr.xmad.sign_b ? GLSLRegister::Type::Integer
: GLSLRegister::Type::UnsignedInteger);
op_c += regs.GetRegisterAsInteger(instr.gpr39, 0, is_signed);
break;
}
case OpCode::Id::XMAD_RR: {
is_merge = instr.xmad.merge_37;
op_b += regs.GetRegisterAsInteger(instr.gpr20, 0, instr.xmad.sign_b);
op_c += regs.GetRegisterAsInteger(instr.gpr39, 0, is_signed);
break;
}
case OpCode::Id::XMAD_RC: {
op_b += regs.GetRegisterAsInteger(instr.gpr39, 0, instr.xmad.sign_b);
op_c += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
is_signed ? GLSLRegister::Type::Integer
: GLSLRegister::Type::UnsignedInteger);
break;
}
case OpCode::Id::XMAD_IMM: {
is_merge = instr.xmad.merge_37;
op_b += std::to_string(instr.xmad.imm20_16);
op_c += regs.GetRegisterAsInteger(instr.gpr39, 0, is_signed);
break;
}
default: {
LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->GetName());
UNREACHABLE();
}
}
// TODO(bunnei): Ensure this is right with signed operands
if (instr.xmad.high_a) {
op_a = "((" + op_a + ") >> 16)";
} else {
op_a = "((" + op_a + ") & 0xFFFF)";
}
std::string src2 = '(' + op_b + ')'; // Preserve original source 2
if (instr.xmad.high_b) {
op_b = '(' + src2 + " >> 16)";
} else {
op_b = '(' + src2 + " & 0xFFFF)";
}
std::string product = '(' + op_a + " * " + op_b + ')';
if (instr.xmad.product_shift_left) {
product = '(' + product + " << 16)";
}
switch (instr.xmad.mode) {
case Tegra::Shader::XmadMode::None:
break;
case Tegra::Shader::XmadMode::CLo:
op_c = "((" + op_c + ") & 0xFFFF)";
break;
case Tegra::Shader::XmadMode::CHi:
op_c = "((" + op_c + ") >> 16)";
break;
case Tegra::Shader::XmadMode::CBcc:
op_c = "((" + op_c + ") + (" + src2 + "<< 16))";
break;
default: {
LOG_CRITICAL(HW_GPU, "Unhandled XMAD mode: {}",
static_cast<u32>(instr.xmad.mode.Value()));
UNREACHABLE();
}
}
std::string sum{'(' + product + " + " + op_c + ')'};
if (is_merge) {
sum = "((" + sum + " & 0xFFFF) | (" + src2 + "<< 16))";
}
regs.SetRegisterToInteger(instr.gpr0, is_signed, 0, sum, 1, 1);
break;
}
default: {
switch (opcode->GetId()) {
case OpCode::Id::EXIT: {
@@ -1762,15 +1667,7 @@ private:
}
case OpCode::Id::KIL: {
ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always);
// Enclose "discard" in a conditional, so that GLSL compilation does not complain
// about unexecuted instructions that may follow this.
shader.AddLine("if (true) {");
++shader.scope;
shader.AddLine("discard;");
--shader.scope;
shader.AddLine("}");
break;
}
case OpCode::Id::BRA: {
@@ -203,6 +203,21 @@ void OpenGLState::Apply() const {
}
}
// Constbuffers
for (std::size_t stage = 0; stage < draw.const_buffers.size(); ++stage) {
for (std::size_t buffer_id = 0; buffer_id < draw.const_buffers[stage].size(); ++buffer_id) {
const auto& current = cur_state.draw.const_buffers[stage][buffer_id];
const auto& new_state = draw.const_buffers[stage][buffer_id];
if (current.enabled != new_state.enabled || current.bindpoint != new_state.bindpoint ||
current.ssbo != new_state.ssbo) {
if (new_state.enabled) {
glBindBufferBase(GL_UNIFORM_BUFFER, new_state.bindpoint, new_state.ssbo);
}
}
}
}
// Framebuffer
if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
@@ -119,6 +119,12 @@ public:
GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING
GLuint shader_program; // GL_CURRENT_PROGRAM
GLuint program_pipeline; // GL_PROGRAM_PIPELINE_BINDING
struct ConstBufferConfig {
bool enabled = false;
GLuint bindpoint;
GLuint ssbo;
};
std::array<std::array<ConstBufferConfig, Regs::MaxConstBuffers>, 5> const_buffers;
} draw;
struct {
@@ -9,91 +9,174 @@
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent)
: gl_target(target), buffer_size(size) {
gl_buffer.Create();
glBindBuffer(gl_target, gl_buffer.handle);
class OrphanBuffer : public OGLStreamBuffer {
public:
explicit OrphanBuffer(GLenum target) : OGLStreamBuffer(target) {}
~OrphanBuffer() override;
GLsizeiptr allocate_size = size;
if (target == GL_ARRAY_BUFFER) {
// On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer
// read position is near the end and is an out-of-bound access to the vertex buffer. This is
// probably a bug in the driver and is related to the usage of vec3<byte> attributes in the
// vertex array. Doubling the allocation size for the vertex buffer seems to avoid the
// crash.
allocate_size *= 2;
}
private:
void Create(size_t size, size_t sync_subdivide) override;
void Release() override;
if (GLAD_GL_ARB_buffer_storage) {
persistent = true;
coherent = prefer_coherent;
GLbitfield flags =
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0);
glBufferStorage(gl_target, allocate_size, nullptr, flags);
mapped_ptr = static_cast<u8*>(glMapBufferRange(
gl_target, 0, buffer_size, flags | (coherent ? 0 : GL_MAP_FLUSH_EXPLICIT_BIT)));
} else {
glBufferData(gl_target, allocate_size, nullptr, GL_STREAM_DRAW);
}
}
std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override;
void Unmap() override;
OGLStreamBuffer::~OGLStreamBuffer() {
if (persistent) {
glBindBuffer(gl_target, gl_buffer.handle);
glUnmapBuffer(gl_target);
}
gl_buffer.Release();
std::vector<u8> data;
};
class StorageBuffer : public OGLStreamBuffer {
public:
explicit StorageBuffer(GLenum target) : OGLStreamBuffer(target) {}
~StorageBuffer() override;
private:
void Create(size_t size, size_t sync_subdivide) override;
void Release() override;
std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) override;
void Unmap() override;
struct Fence {
OGLSync sync;
size_t offset;
};
std::deque<Fence> head;
std::deque<Fence> tail;
u8* mapped_ptr;
};
OGLStreamBuffer::OGLStreamBuffer(GLenum target) {
gl_target = target;
}
GLuint OGLStreamBuffer::GetHandle() const {
return gl_buffer.handle;
}
GLsizeiptr OGLStreamBuffer::GetSize() const {
return buffer_size;
std::unique_ptr<OGLStreamBuffer> OGLStreamBuffer::MakeBuffer(bool storage_buffer, GLenum target) {
if (storage_buffer) {
return std::make_unique<StorageBuffer>(target);
}
return std::make_unique<OrphanBuffer>(target);
}
std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr alignment) {
ASSERT(size <= buffer_size);
ASSERT(alignment <= buffer_size);
mapped_size = size;
OrphanBuffer::~OrphanBuffer() {
Release();
}
if (alignment > 0) {
buffer_pos = Common::AlignUp<size_t>(buffer_pos, alignment);
void OrphanBuffer::Create(size_t size, size_t /*sync_subdivide*/) {
buffer_pos = 0;
buffer_size = size;
data.resize(buffer_size);
if (gl_buffer.handle == 0) {
gl_buffer.Create();
glBindBuffer(gl_target, gl_buffer.handle);
}
bool invalidate = false;
glBufferData(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr, GL_STREAM_DRAW);
}
void OrphanBuffer::Release() {
gl_buffer.Release();
}
std::pair<u8*, GLintptr> OrphanBuffer::Map(size_t size, size_t alignment) {
buffer_pos = Common::AlignUp(buffer_pos, alignment);
if (buffer_pos + size > buffer_size) {
buffer_pos = 0;
invalidate = true;
Create(std::max(buffer_size, size), 0);
}
if (persistent) {
glUnmapBuffer(gl_target);
mapped_size = size;
return std::make_pair(&data[buffer_pos], static_cast<GLintptr>(buffer_pos));
}
void OrphanBuffer::Unmap() {
glBufferSubData(gl_target, static_cast<GLintptr>(buffer_pos),
static_cast<GLsizeiptr>(mapped_size), &data[buffer_pos]);
buffer_pos += mapped_size;
}
StorageBuffer::~StorageBuffer() {
Release();
}
void StorageBuffer::Create(size_t size, size_t sync_subdivide) {
if (gl_buffer.handle != 0)
return;
buffer_pos = 0;
buffer_size = size;
buffer_sync_subdivide = std::max<size_t>(sync_subdivide, 1);
gl_buffer.Create();
glBindBuffer(gl_target, gl_buffer.handle);
glBufferStorage(gl_target, static_cast<GLsizeiptr>(buffer_size), nullptr,
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT);
mapped_ptr = reinterpret_cast<u8*>(
glMapBufferRange(gl_target, 0, static_cast<GLsizeiptr>(buffer_size),
GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT));
}
void StorageBuffer::Release() {
if (gl_buffer.handle == 0)
return;
glUnmapBuffer(gl_target);
gl_buffer.Release();
head.clear();
tail.clear();
}
std::pair<u8*, GLintptr> StorageBuffer::Map(size_t size, size_t alignment) {
ASSERT(size <= buffer_size);
OGLSync sync;
buffer_pos = Common::AlignUp(buffer_pos, alignment);
size_t effective_offset = Common::AlignDown(buffer_pos, buffer_sync_subdivide);
if (!head.empty() &&
(effective_offset > head.back().offset || buffer_pos + size > buffer_size)) {
ASSERT(head.back().sync.handle == 0);
head.back().sync.Create();
}
if (buffer_pos + size > buffer_size) {
if (!tail.empty()) {
std::swap(sync, tail.back().sync);
tail.clear();
}
std::swap(tail, head);
buffer_pos = 0;
effective_offset = 0;
}
if (invalidate | !persistent) {
GLbitfield flags = GL_MAP_WRITE_BIT | (persistent ? GL_MAP_PERSISTENT_BIT : 0) |
(coherent ? GL_MAP_COHERENT_BIT : GL_MAP_FLUSH_EXPLICIT_BIT) |
(invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT);
mapped_ptr = static_cast<u8*>(
glMapBufferRange(gl_target, buffer_pos, buffer_size - buffer_pos, flags));
mapped_offset = buffer_pos;
while (!tail.empty() && buffer_pos + size > tail.front().offset) {
std::swap(sync, tail.front().sync);
tail.pop_front();
}
return std::make_tuple(mapped_ptr + buffer_pos - mapped_offset, buffer_pos, invalidate);
if (sync.handle != 0) {
glClientWaitSync(sync.handle, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
sync.Release();
}
if (head.empty() || effective_offset > head.back().offset) {
head.emplace_back();
head.back().offset = effective_offset;
}
mapped_size = size;
return std::make_pair(&mapped_ptr[buffer_pos], static_cast<GLintptr>(buffer_pos));
}
void OGLStreamBuffer::Unmap(GLsizeiptr size) {
ASSERT(size <= mapped_size);
if (!coherent && size > 0) {
glFlushMappedBufferRange(gl_target, buffer_pos - mapped_offset, size);
}
if (!persistent) {
glUnmapBuffer(gl_target);
}
buffer_pos += size;
void StorageBuffer::Unmap() {
glFlushMappedBufferRange(gl_target, static_cast<GLintptr>(buffer_pos),
static_cast<GLsizeiptr>(mapped_size));
buffer_pos += mapped_size;
}
@@ -2,41 +2,35 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <tuple>
#pragma once
#include <memory>
#include <glad/glad.h>
#include "common/common_types.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
class OGLStreamBuffer : private NonCopyable {
public:
explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent = false);
~OGLStreamBuffer();
explicit OGLStreamBuffer(GLenum target);
virtual ~OGLStreamBuffer() = default;
public:
static std::unique_ptr<OGLStreamBuffer> MakeBuffer(bool storage_buffer, GLenum target);
virtual void Create(size_t size, size_t sync_subdivide) = 0;
virtual void Release() {}
GLuint GetHandle() const;
GLsizeiptr GetSize() const;
/*
* Allocates a linear chunk of memory in the GPU buffer with at least "size" bytes
* and the optional alignment requirement.
* If the buffer is full, the whole buffer is reallocated which invalidates old chunks.
* The return values are the pointer to the new chunk, the offset within the buffer,
* and the invalidation flag for previous chunks.
* The actual used size must be specified on unmapping the chunk.
*/
std::tuple<u8*, GLintptr, bool> Map(GLsizeiptr size, GLintptr alignment = 0);
virtual std::pair<u8*, GLintptr> Map(size_t size, size_t alignment) = 0;
virtual void Unmap() = 0;
void Unmap(GLsizeiptr size);
private:
protected:
OGLBuffer gl_buffer;
GLenum gl_target;
bool coherent = false;
bool persistent = false;
GLintptr buffer_pos = 0;
GLsizeiptr buffer_size = 0;
GLintptr mapped_offset = 0;
GLsizeiptr mapped_size = 0;
u8* mapped_ptr = nullptr;
size_t buffer_pos = 0;
size_t buffer_size = 0;
size_t buffer_sync_subdivide = 0;
size_t mapped_size = 0;
};
@@ -91,8 +91,6 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
switch (topology) {
case Maxwell::PrimitiveTopology::Points:
return GL_POINTS;
case Maxwell::PrimitiveTopology::LineStrip:
return GL_LINE_STRIP;
case Maxwell::PrimitiveTopology::Triangles:
return GL_TRIANGLES;
case Maxwell::PrimitiveTopology::TriangleStrip:
+74 -22
View File
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <regex>
#include <QApplication>
#include <QDir>
#include <QFileInfo>
@@ -402,12 +403,72 @@ void GameList::RefreshGameDirectory() {
}
}
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
std::vector<u8>& icon, std::string& name) {
const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
if (control_dir == nullptr)
return;
const auto nca_control_callback =
[this, &nca_control_map](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
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;
}
}
}
void GameListWorker::AddInstalledTitlesToGameList() {
const auto usernand = Service::FileSystem::GetUserNANDContents();
const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
FileSys::ContentRecordType::Program);
for (const auto& game : installed_games) {
const auto& file = usernand->GetEntryRaw(game);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader)
continue;
std::vector<u8> icon;
std::string name;
u64 program_id;
loader->ReadProgramId(program_id);
const auto& control =
usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
if (control != nullptr)
GetMetadataFromControlNCA(control, icon, name);
emit EntryReady({
new GameListItemPath(
FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
program_id),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(file->GetSize()),
});
}
const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application,
FileSys::ContentRecordType::Control);
for (const auto& entry : control_data) {
const auto nca = usernand->GetEntry(entry);
if (nca != nullptr)
nca_control_map.insert_or_assign(entry.title_id, nca);
}
}
void GameListWorker::FillControlMap(const std::string& dir_path) {
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing)
@@ -425,10 +486,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
};
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
}
const auto callback = [this, recursion,
&nca_control_map](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
const std::string& virtual_name) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing)
@@ -458,20 +520,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
// Use from metadata pool.
if (nca_control_map.find(program_id) != nca_control_map.end()) {
const auto nca = nca_control_map[program_id];
const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
const auto nacp_file = control_dir->GetFile("control.nacp");
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;
}
}
GetMetadataFromControlNCA(nca, icon, name);
}
}
@@ -498,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
void GameListWorker::run() {
stop_processing = false;
watch_list.append(dir_path);
FillControlMap(dir_path.toStdString());
AddInstalledTitlesToGameList();
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
nca_control_map.clear();
emit Finished(watch_list);
}
+3
View File
@@ -163,10 +163,13 @@ signals:
private:
FileSys::VirtualFilesystem vfs;
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
QStringList watch_list;
QString dir_path;
bool deep_scan;
std::atomic_bool stop_processing;
void AddInstalledTitlesToGameList();
void FillControlMap(const std::string& dir_path);
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
};
+143
View File
@@ -24,6 +24,7 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
@@ -114,6 +115,9 @@ GMainWindow::GMainWindow()
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
show();
// Necessary to load titles from nand in gamelist.
Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory(
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
// Show one-time "callout" messages to the user
@@ -309,6 +313,8 @@ void GMainWindow::ConnectMenuEvents() {
// File
connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
connect(ui.action_Install_File_NAND, &QAction::triggered, this,
&GMainWindow::OnMenuInstallToNAND);
connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
&GMainWindow::OnMenuSelectGameListRoot);
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
@@ -612,6 +618,143 @@ void GMainWindow::OnMenuLoadFolder() {
}
}
void GMainWindow::OnMenuInstallToNAND() {
const QString file_filter =
tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
"Image (*.xci)");
QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
UISettings::values.roms_path, file_filter);
const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
if (src == nullptr || dest == nullptr)
return false;
if (!dest->Resize(src->GetSize()))
return false;
QProgressDialog progress(fmt::format("Installing file \"{}\"...", src->GetName()).c_str(),
"Cancel", 0, src->GetSize() / 0x1000, this);
progress.setWindowModality(Qt::WindowModal);
std::array<u8, 0x1000> buffer{};
for (size_t i = 0; i < src->GetSize(); i += 0x1000) {
if (progress.wasCanceled()) {
dest->Resize(0);
return false;
}
progress.setValue(i / 0x1000);
const auto read = src->Read(buffer.data(), buffer.size(), i);
dest->Write(buffer.data(), read, i);
}
return true;
};
const auto success = [this]() {
QMessageBox::information(this, tr("Successfully Installed"),
tr("The file was successfully installed."));
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
};
const auto failed = [this]() {
QMessageBox::warning(
this, tr("Failed to Install"),
tr("There was an error while attempting to install the provided file. It "
"could have an incorrect format or be missing metadata. Please "
"double-check your file and try again."));
};
const auto overwrite = [this]() {
return QMessageBox::question(this, "Failed to Install",
"The file you are attempting to install already exists "
"in the cache. Would you like to overwrite it?") ==
QMessageBox::Yes;
};
if (!filename.isEmpty()) {
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) {
failed();
return;
}
const auto res =
Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, 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);
if (res2 == FileSys::InstallResult::Success) {
success();
} else {
failed();
}
}
} else {
failed();
}
}
} else {
const auto nca = std::make_shared<FileSys::NCA>(
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
if (nca->GetStatus() != Loader::ResultStatus::Success) {
failed();
return;
}
static const QStringList tt_options{"System Application",
"System Archive",
"System Application Update",
"Firmware Package (Type A)",
"Firmware Package (Type B)",
"Game",
"Game Update",
"Game DLC",
"Delta Title"};
bool ok;
const auto item = QInputDialog::getItem(
this, tr("Select NCA Install Type..."),
tr("Please select the type of title you would like to install this NCA as:\n(In "
"most instances, the default 'Game' is fine.)"),
tt_options, 5, false, &ok);
auto index = tt_options.indexOf(item);
if (!ok || index == -1) {
QMessageBox::warning(this, tr("Failed to Install"),
tr("The title type you selected for the NCA is invalid."));
return;
}
if (index >= 5)
index += 0x7B;
const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
nca, static_cast<FileSys::TitleType>(index), 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(
nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
if (res2 == FileSys::InstallResult::Success) {
success();
} else {
failed();
}
}
} else {
failed();
}
}
}
}
}
void GMainWindow::OnMenuSelectGameListRoot() {
QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!dir_path.isEmpty()) {
+1
View File
@@ -125,6 +125,7 @@ private slots:
void OnGameListOpenSaveFolder(u64 program_id);
void OnMenuLoadFile();
void OnMenuLoadFolder();
void OnMenuInstallToNAND();
/// Called whenever a user selects the "File->Select Game List Root" menu item
void OnMenuSelectGameListRoot();
void OnMenuRecentFile();
+7
View File
@@ -57,6 +57,8 @@
<string>Recent Files</string>
</property>
</widget>
<addaction name="action_Install_File_NAND" />
<addaction name="separator"/>
<addaction name="action_Load_File"/>
<addaction name="action_Load_Folder"/>
<addaction name="separator"/>
@@ -102,6 +104,11 @@
<addaction name="menu_View"/>
<addaction name="menu_Help"/>
</widget>
<action name="action_Install_File_NAND">
<property name="text">
<string>Install File to NAND...</string>
</property>
</action>
<action name="action_Load_File">
<property name="text">
<string>Load File...</string>