Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64dbc92b61 | |||
| 3bfba23362 | |||
| cf1e4770f9 | |||
| d961d5479e | |||
| 9187350b32 | |||
| fa1c60c33e | |||
| 2a4730cbee | |||
| 2bddc03468 | |||
| e34899067b | |||
| 885ea2de2a | |||
| 94afffe9e5 | |||
| a1b8e5d09a | |||
| 682174b112 | |||
| 3e729c13cc | |||
| 37850eeee5 | |||
| a0055192fe | |||
| c6becfc9f5 | |||
| 7d41c1f523 | |||
| 12aa127df3 | |||
| 470466b31b | |||
| c9ccdfbeac | |||
| 7979ccd956 | |||
| 8b857fc7c2 | |||
| ae9604faba | |||
| 361a8fa318 | |||
| 8dd2e91427 | |||
| daf9cd9358 | |||
| 787b191abf | |||
| 038bcec111 | |||
| 2b514275ad | |||
| 9286976948 | |||
| ccd70819c2 | |||
| a49169e819 | |||
| d4d38dd44d | |||
| c182688ad6 | |||
| 2590b5a9ea | |||
| 918119ae1b | |||
| c6ff4a6f4d | |||
| faf628ad8d | |||
| ccaafaccfc | |||
| 77f9ecd32b | |||
| e018a48460 | |||
| b4164d295b | |||
| 4b91057688 | |||
| 1b04b72653 | |||
| 43af31836e | |||
| 721a92775d | |||
| e47b57a90f | |||
| 8abbc619a1 | |||
| 0a8e540681 | |||
| 08c0783d34 | |||
| 0084cceb20 | |||
| 02b36b0eb5 | |||
| 49c44e3fae | |||
| 62d772eaed | |||
| 06db4d94fd | |||
| 9d9fc8a675 | |||
| 8500ca797f | |||
| 256a50ad15 | |||
| b71bda45ae | |||
| 9bee885282 | |||
| 4dae5a52a8 | |||
| 3a1899d143 | |||
| 527b841c15 | |||
| 8b76444916 | |||
| 97b8c9d2c3 | |||
| 8fd266a7c4 | |||
| 183c445c30 | |||
| c7c8ffbc13 | |||
| 25383b9ff2 | |||
| c41365a56f | |||
| 9ad42fb0cf | |||
| b4db662053 | |||
| 934ce530f6 | |||
| b9fd1e2bed | |||
| 41836f3a17 | |||
| 01a4afee42 | |||
| c2f966dbc1 | |||
| bbe82d62b0 | |||
| 88d857499b | |||
| 4b81d19a1a | |||
| 50259d7bdc | |||
| b31880dc5e | |||
| 0526bf1895 | |||
| 2dd6411753 | |||
| 8d778c90e2 | |||
| d36a7a43c5 | |||
| 684b616f0d | |||
| 17a9b0178d | |||
| 0f7b813d65 | |||
| 4de04eba39 | |||
| f17415d431 | |||
| b957a4862f | |||
| 1c75945dc4 |
@@ -6,9 +6,11 @@ yuzu emulator
|
||||
|
||||
yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/).
|
||||
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows, Linux and macOS. The emulator is currently only useful for homebrew development and research purposes.
|
||||
It is written in C++ with portability in mind, with builds actively maintained for Windows and Linux. The emulator is capable of running several commercial games.
|
||||
|
||||
yuzu only emulates a subset of Switch hardware and therefore is generally only useful for running/debugging homebrew applications. yuzu can boot some games, to varying degrees of success.
|
||||
yuzu only emulates a subset of Switch hardware and therefore most commercial games **do not** run at full speed or are not fully functional.
|
||||
|
||||
Do you want to check which games are compatible and which ones are not? Please visit our [Compatibility page](https://yuzu-emu.org/game/)!
|
||||
|
||||
yuzu is licensed under the GPLv2 (or any later version). Refer to the license.txt file included.
|
||||
|
||||
|
||||
Vendored
+1
-1
Submodule externals/dynarmic updated: 2683a9a3e3...087a74417a
@@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
||||
CLS(Input) \
|
||||
CLS(Network) \
|
||||
CLS(Loader) \
|
||||
CLS(CheatEngine) \
|
||||
CLS(Crypto) \
|
||||
CLS(WebService)
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ enum class Class : ClassType {
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
Loader, ///< ROM loader
|
||||
CheatEngine, ///< Memory manipulation and engine VM functions
|
||||
Crypto, ///< Cryptographic engine/functions
|
||||
Input, ///< Input emulation
|
||||
Network, ///< Network emulation
|
||||
|
||||
@@ -33,8 +33,6 @@ add_library(core STATIC
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/cheat_engine.cpp
|
||||
file_sys/cheat_engine.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -477,6 +475,11 @@ add_library(core STATIC
|
||||
loader/nsp.h
|
||||
loader/xci.cpp
|
||||
loader/xci.h
|
||||
memory/cheat_engine.cpp
|
||||
memory/cheat_engine.h
|
||||
memory/dmnt_cheat_types.h
|
||||
memory/dmnt_cheat_vm.cpp
|
||||
memory/dmnt_cheat_vm.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_setup.h
|
||||
|
||||
+56
-18
@@ -14,8 +14,14 @@
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_core_manager.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
@@ -27,17 +33,17 @@
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/apm/controller.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/glue/manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "file_sys/patch_manager.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
@@ -160,10 +166,6 @@ struct System::Impl {
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
GetAndResetPerfStats();
|
||||
perf_stats.BeginSystemFrame();
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
@@ -202,10 +204,34 @@ struct System::Impl {
|
||||
gpu_core->Start();
|
||||
cpu_core_manager.StartThreads();
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
cheat_engine->Initialize();
|
||||
}
|
||||
|
||||
// All threads are started, begin main process execution, now that we're in the clear.
|
||||
main_process->Run(load_parameters->main_thread_priority,
|
||||
load_parameters->main_thread_stack_size);
|
||||
|
||||
if (Settings::values.gamecard_inserted) {
|
||||
if (Settings::values.gamecard_current_game) {
|
||||
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
|
||||
} else if (!Settings::values.gamecard_path.empty()) {
|
||||
fs_controller.SetGameCard(
|
||||
GetGameFileFromPath(virtual_filesystem, Settings::values.gamecard_path));
|
||||
}
|
||||
}
|
||||
|
||||
u64 title_id{0};
|
||||
if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
perf_stats = std::make_unique<PerfStats>(title_id);
|
||||
// Reset counters and set time origin to current frame
|
||||
GetAndResetPerfStats();
|
||||
perf_stats->BeginSystemFrame();
|
||||
|
||||
status = ResultStatus::Success;
|
||||
return status;
|
||||
}
|
||||
@@ -219,6 +245,8 @@ struct System::Impl {
|
||||
perf_results.game_fps);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS",
|
||||
perf_stats->GetMeanFrametime());
|
||||
|
||||
is_powered_on = false;
|
||||
|
||||
@@ -229,6 +257,7 @@ struct System::Impl {
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
perf_stats.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
@@ -286,7 +315,7 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
PerfStatsResults GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
Timing::CoreTiming core_timing;
|
||||
@@ -295,6 +324,7 @@ struct System::Impl {
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
/// ContentProviderUnion instance
|
||||
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
|
||||
Service::FileSystem::FileSystemController fs_controller;
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
@@ -304,7 +334,7 @@ struct System::Impl {
|
||||
CpuCoreManager cpu_core_manager;
|
||||
bool is_powered_on = false;
|
||||
|
||||
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Memory::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
|
||||
/// Frontend applets
|
||||
@@ -327,7 +357,7 @@ struct System::Impl {
|
||||
ResultStatus status = ResultStatus::Success;
|
||||
std::string status_details = "";
|
||||
|
||||
Core::PerfStats perf_stats;
|
||||
std::unique_ptr<Core::PerfStats> perf_stats;
|
||||
Core::FrameLimiter frame_limiter;
|
||||
};
|
||||
|
||||
@@ -480,11 +510,11 @@ const Timing::CoreTiming& System::CoreTiming() const {
|
||||
}
|
||||
|
||||
Core::PerfStats& System::GetPerfStats() {
|
||||
return impl->perf_stats;
|
||||
return *impl->perf_stats;
|
||||
}
|
||||
|
||||
const Core::PerfStats& System::GetPerfStats() const {
|
||||
return impl->perf_stats;
|
||||
return *impl->perf_stats;
|
||||
}
|
||||
|
||||
Core::FrameLimiter& System::FrameLimiter() {
|
||||
@@ -519,13 +549,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
|
||||
return impl->debug_context.get();
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end) {
|
||||
impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
|
||||
code_region_start, code_region_end);
|
||||
}
|
||||
|
||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
||||
impl->virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
@@ -534,6 +557,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
|
||||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 32>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size) {
|
||||
impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
|
||||
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
||||
}
|
||||
|
||||
void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) {
|
||||
impl->applet_manager.SetAppletFrontendSet(std::move(set));
|
||||
}
|
||||
@@ -562,6 +592,14 @@ const FileSys::ContentProvider& System::GetContentProvider() const {
|
||||
return *impl->content_provider;
|
||||
}
|
||||
|
||||
Service::FileSystem::FileSystemController& System::GetFileSystemController() {
|
||||
return impl->fs_controller;
|
||||
}
|
||||
|
||||
const Service::FileSystem::FileSystemController& System::GetFileSystemController() const {
|
||||
return impl->fs_controller;
|
||||
}
|
||||
|
||||
void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider) {
|
||||
impl->content_provider->SetSlot(slot, provider);
|
||||
|
||||
+15
-3
@@ -18,7 +18,6 @@ class EmuWindow;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class ContentProvider;
|
||||
class ContentProviderUnion;
|
||||
enum class ContentProviderUnionSlot;
|
||||
@@ -36,6 +35,10 @@ class AppLoader;
|
||||
enum class ResultStatus : u16;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Memory {
|
||||
struct CheatEntry;
|
||||
} // namespace Memory
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace AM::Applets {
|
||||
@@ -47,6 +50,10 @@ namespace APM {
|
||||
class Controller;
|
||||
}
|
||||
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
} // namespace FileSystem
|
||||
|
||||
namespace Glue {
|
||||
class ARPManager;
|
||||
}
|
||||
@@ -282,8 +289,9 @@ public:
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size);
|
||||
|
||||
void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
|
||||
|
||||
@@ -299,6 +307,10 @@ public:
|
||||
|
||||
const FileSys::ContentProvider& GetContentProvider() const;
|
||||
|
||||
Service::FileSystem::FileSystemController& GetFileSystemController();
|
||||
|
||||
const Service::FileSystem::FileSystemController& GetFileSystemController() const;
|
||||
|
||||
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider);
|
||||
|
||||
|
||||
@@ -480,6 +480,10 @@ void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
|
||||
prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetDecryptedProdInfo() const {
|
||||
return prodinfo_decrypted;
|
||||
}
|
||||
|
||||
std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
|
||||
std::array<u8, 0x240> out{};
|
||||
if (prodinfo_decrypted != nullptr)
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
bool HasProdInfo() const;
|
||||
FileSys::VirtualFile GetProdInfoRaw() const;
|
||||
void DecryptProdInfo(std::array<u8, 0x20> bis_key);
|
||||
FileSys::VirtualFile GetDecryptedProdInfo() const;
|
||||
std::array<u8, 0x240> GetETicketExtendedKek() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -14,10 +18,22 @@ BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir
|
||||
sysnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))),
|
||||
sysnand_placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/placehld"))),
|
||||
usrnand_placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/placehld"))) {}
|
||||
|
||||
BISFactory::~BISFactory() = default;
|
||||
|
||||
VirtualDir BISFactory::GetSystemNANDContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/system/Contents");
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetUserNANDContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user/Contents");
|
||||
}
|
||||
|
||||
RegisteredCache* BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache.get();
|
||||
}
|
||||
@@ -26,9 +42,17 @@ RegisteredCache* BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* BISFactory::GetSystemNANDPlaceholder() const {
|
||||
return sysnand_placeholder.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* BISFactory::GetUserNANDPlaceholder() const {
|
||||
return usrnand_placeholder.get();
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
|
||||
// LayeredFS doesn't work on updates and title id-less homebrew
|
||||
if (title_id == 0 || (title_id & 0x800) > 0)
|
||||
if (title_id == 0 || (title_id & 0xFFF) == 0x800)
|
||||
return nullptr;
|
||||
return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
@@ -39,4 +63,77 @@ VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const {
|
||||
return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationFile:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/prodinfof");
|
||||
case BisPartitionId::SafeMode:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/safe");
|
||||
case BisPartitionId::System:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/system");
|
||||
case BisPartitionId::User:
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user");
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
|
||||
Core::Crypto::KeyManager keys;
|
||||
Core::Crypto::PartitionDataManager pdm{
|
||||
Core::System::GetInstance().GetFilesystem()->OpenDirectory(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)};
|
||||
keys.PopulateFromPartitionData(pdm);
|
||||
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationBinary:
|
||||
return pdm.GetDecryptedProdInfo();
|
||||
case BisPartitionId::BootConfigAndPackage2Part1:
|
||||
case BisPartitionId::BootConfigAndPackage2Part2:
|
||||
case BisPartitionId::BootConfigAndPackage2Part3:
|
||||
case BisPartitionId::BootConfigAndPackage2Part4:
|
||||
case BisPartitionId::BootConfigAndPackage2Part5:
|
||||
case BisPartitionId::BootConfigAndPackage2Part6: {
|
||||
const auto new_id = static_cast<u8>(id) -
|
||||
static_cast<u8>(BisPartitionId::BootConfigAndPackage2Part1) +
|
||||
static_cast<u8>(Core::Crypto::Package2Type::NormalMain);
|
||||
return pdm.GetPackage2Raw(static_cast<Core::Crypto::Package2Type>(new_id));
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetImageDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(nand_root, "/user/Album");
|
||||
}
|
||||
|
||||
u64 BISFactory::GetSystemNANDFreeSpace() const {
|
||||
const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system");
|
||||
if (sys_dir == nullptr)
|
||||
return 0;
|
||||
|
||||
return GetSystemNANDTotalSpace() - sys_dir->GetSize();
|
||||
}
|
||||
|
||||
u64 BISFactory::GetSystemNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_system_size);
|
||||
}
|
||||
|
||||
u64 BISFactory::GetUserNANDFreeSpace() const {
|
||||
const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user");
|
||||
if (usr_dir == nullptr)
|
||||
return 0;
|
||||
|
||||
return GetUserNANDTotalSpace() - usr_dir->GetSize();
|
||||
}
|
||||
|
||||
u64 BISFactory::GetUserNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_user_size);
|
||||
}
|
||||
|
||||
u64 BISFactory::GetFullNANDTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.nand_total_size);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -10,7 +10,25 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class BisPartitionId : u32 {
|
||||
UserDataRoot = 20,
|
||||
CalibrationBinary = 27,
|
||||
CalibrationFile = 28,
|
||||
BootConfigAndPackage2Part1 = 21,
|
||||
BootConfigAndPackage2Part2 = 22,
|
||||
BootConfigAndPackage2Part3 = 23,
|
||||
BootConfigAndPackage2Part4 = 24,
|
||||
BootConfigAndPackage2Part5 = 25,
|
||||
BootConfigAndPackage2Part6 = 26,
|
||||
SafeMode = 29,
|
||||
System = 31,
|
||||
SystemProperEncryption = 32,
|
||||
SystemProperPartition = 33,
|
||||
User = 30,
|
||||
};
|
||||
|
||||
class RegisteredCache;
|
||||
class PlaceholderCache;
|
||||
|
||||
/// 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
|
||||
@@ -20,12 +38,29 @@ public:
|
||||
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root);
|
||||
~BISFactory();
|
||||
|
||||
VirtualDir GetSystemNANDContentDirectory() const;
|
||||
VirtualDir GetUserNANDContentDirectory() const;
|
||||
|
||||
RegisteredCache* GetSystemNANDContents() const;
|
||||
RegisteredCache* GetUserNANDContents() const;
|
||||
|
||||
PlaceholderCache* GetSystemNANDPlaceholder() const;
|
||||
PlaceholderCache* GetUserNANDPlaceholder() const;
|
||||
|
||||
VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
VirtualDir GetModificationDumpRoot(u64 title_id) const;
|
||||
|
||||
VirtualDir OpenPartition(BisPartitionId id) const;
|
||||
VirtualFile OpenPartitionStorage(BisPartitionId id) const;
|
||||
|
||||
VirtualDir GetImageDirectory() const;
|
||||
|
||||
u64 GetSystemNANDFreeSpace() const;
|
||||
u64 GetSystemNANDTotalSpace() const;
|
||||
u64 GetUserNANDFreeSpace() const;
|
||||
u64 GetUserNANDTotalSpace() const;
|
||||
u64 GetFullNANDTotalSpace() const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
@@ -33,6 +68,9 @@ private:
|
||||
|
||||
std::unique_ptr<RegisteredCache> sysnand_cache;
|
||||
std::unique_ptr<RegisteredCache> usrnand_cache;
|
||||
|
||||
std::unique_ptr<PlaceholderCache> sysnand_placeholder;
|
||||
std::unique_ptr<PlaceholderCache> usrnand_placeholder;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -12,12 +12,16 @@
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
|
||||
constexpr std::array partition_names{
|
||||
"update",
|
||||
"normal",
|
||||
@@ -175,6 +179,26 @@ VirtualDir XCI::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
VirtualDir XCI::ConcatenatedPseudoDirectory() {
|
||||
const auto out = std::make_shared<VectorVfsDirectory>();
|
||||
for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
|
||||
const auto& part = GetPartition(part_id);
|
||||
if (part == nullptr)
|
||||
continue;
|
||||
|
||||
for (const auto& file : part->GetFiles())
|
||||
out->AddFile(file);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 0x200> XCI::GetCertificate() const {
|
||||
std::array<u8, 0x200> out;
|
||||
file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
|
||||
return out;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
const auto partition_index = static_cast<std::size_t>(part);
|
||||
const auto& partition = partitions[partition_index];
|
||||
|
||||
@@ -91,6 +91,8 @@ public:
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
u64 GetProgramTitleID() const;
|
||||
u32 GetSystemUpdateVersion();
|
||||
u64 GetSystemUpdateTitleID() const;
|
||||
|
||||
bool HasProgramNCA() const;
|
||||
VirtualFile GetProgramNCAFile() const;
|
||||
@@ -106,6 +108,11 @@ public:
|
||||
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
// Creates a directory that contains all the NCAs in the gamecard
|
||||
VirtualDir ConcatenatedPseudoDirectory();
|
||||
|
||||
std::array<u8, 0x200> GetCertificate() const;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
|
||||
|
||||
@@ -120,6 +127,8 @@ private:
|
||||
std::shared_ptr<NCA> program;
|
||||
std::vector<std::shared_ptr<NCA>> ncas;
|
||||
|
||||
u64 update_normal_partition_end;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -1,492 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
|
||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
|
||||
|
||||
u64 Cheat::Address() const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u64));
|
||||
return Common::swap64(out) & 0xFFFFFFFFFF;
|
||||
}
|
||||
|
||||
u64 Cheat::ValueWidth(u64 offset) const {
|
||||
return Value(offset, width);
|
||||
}
|
||||
|
||||
u64 Cheat::Value(u64 offset, u64 width) const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data() + offset, sizeof(u64));
|
||||
out = Common::swap64(out);
|
||||
if (width == 8)
|
||||
return out;
|
||||
return out & ((1ull << (width * CHAR_BIT)) - 1);
|
||||
}
|
||||
|
||||
u32 Cheat::KeypadValue() const {
|
||||
u32 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u32));
|
||||
return Common::swap32(out) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
|
||||
VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
|
||||
this->main_region_begin = main_begin;
|
||||
this->main_region_end = main_end;
|
||||
this->heap_region_begin = heap_begin;
|
||||
this->heap_region_end = heap_end;
|
||||
this->writer = writer;
|
||||
this->reader = reader;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
||||
|
||||
void CheatList::Execute() {
|
||||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
std::fill(scratch.begin(), scratch.end(), 0);
|
||||
in_standard = false;
|
||||
for (std::size_t i = 0; i < master_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(master_list[i].second);
|
||||
}
|
||||
|
||||
in_standard = true;
|
||||
for (std::size_t i = 0; i < standard_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(standard_list[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
|
||||
: master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
|
||||
|
||||
bool CheatList::EvaluateConditional(const Cheat& cheat) const {
|
||||
using ComparisonFunction = bool (*)(u64, u64);
|
||||
constexpr std::array<ComparisonFunction, 6> comparison_functions{
|
||||
[](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
|
||||
[](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
|
||||
[](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
|
||||
};
|
||||
|
||||
if (cheat.type == CodeType::ConditionalInput) {
|
||||
const auto applet_resource =
|
||||
system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
|
||||
if (applet_resource == nullptr) {
|
||||
LOG_WARNING(
|
||||
Common_Filesystem,
|
||||
"Attempted to evaluate input conditional, but applet resource is not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto press_state =
|
||||
applet_resource
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
|
||||
.GetAndResetPressState();
|
||||
return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
|
||||
}
|
||||
|
||||
ASSERT(cheat.type == CodeType::Conditional);
|
||||
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
|
||||
auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
|
||||
const auto addr = cheat.Address() + offset;
|
||||
|
||||
return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::ProcessBlockPairs(const Block& block) {
|
||||
block_pairs.clear();
|
||||
|
||||
u64 scope = 0;
|
||||
std::map<u64, u64> pairs;
|
||||
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
const auto& cheat = block[i];
|
||||
|
||||
switch (cheat.type) {
|
||||
case CodeType::Conditional:
|
||||
case CodeType::ConditionalInput:
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
break;
|
||||
case CodeType::EndConditional: {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
break;
|
||||
}
|
||||
case CodeType::Loop: {
|
||||
if (cheat.end_of_loop) {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
} else {
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatList::WriteImmediate(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = cheat.Address() + offset + register_3;
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
|
||||
cheat.Value(8, cheat.width));
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditional(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
void CheatList::EndConditional(const Cheat& cheat) {
|
||||
LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
|
||||
}
|
||||
|
||||
void CheatList::Loop(const Cheat& cheat) {
|
||||
if (cheat.end_of_loop.Value())
|
||||
ASSERT(!cheat.end_of_loop.Value());
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
ASSERT(iter->first < iter->second);
|
||||
|
||||
const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
|
||||
for (s32 i = initial_value; i >= 0; --i) {
|
||||
register_3 = static_cast<u64>(i);
|
||||
for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
|
||||
current_index = c;
|
||||
ExecuteSingleCheat(
|
||||
(in_standard ? standard_list : master_list)[current_block].second[c]);
|
||||
}
|
||||
}
|
||||
|
||||
current_index = iter->second;
|
||||
}
|
||||
|
||||
void CheatList::LoadImmediate(const Cheat& cheat) {
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
|
||||
cheat.Value(4, 8));
|
||||
register_3 = cheat.Value(4, 8);
|
||||
}
|
||||
|
||||
void CheatList::LoadIndexed(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
|
||||
LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
|
||||
cheat.register_3, addr);
|
||||
register_3 = reader(cheat.width, SanitizeAddress(addr));
|
||||
}
|
||||
|
||||
void CheatList::StoreIndexed(const Cheat& cheat) {
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr =
|
||||
register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
|
||||
cheat.Value(4, cheat.width), addr);
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::RegisterArithmetic(const Cheat& cheat) {
|
||||
using ArithmeticFunction = u64 (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
|
||||
[](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
|
||||
[](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
|
||||
[](u64 a, u64 b) { return a >> b; },
|
||||
};
|
||||
|
||||
using ArithmeticOverflowCheck = bool (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
|
||||
};
|
||||
|
||||
static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
|
||||
"Missing or have extra arithmetic overflow checks compared to functions!");
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
|
||||
auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
auto* overflow_function =
|
||||
arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
|
||||
cheat.register_3, cheat.ValueWidth(4));
|
||||
|
||||
if (overflow_function(register_3, cheat.ValueWidth(4))) {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"overflow will occur when performing arithmetic operation={:02X} with operands "
|
||||
"a={:016X}, b={:016X}!",
|
||||
static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
register_3 = function(register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditionalInput(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat))
|
||||
return;
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
VAddr CheatList::SanitizeAddress(VAddr in) const {
|
||||
if ((in < main_region_begin || in >= main_region_end) &&
|
||||
(in < heap_region_begin || in >= heap_region_end)) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
|
||||
using CheatOperationFunction = void (CheatList::*)(const Cheat&);
|
||||
constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
|
||||
&CheatList::WriteImmediate, &CheatList::BeginConditional,
|
||||
&CheatList::EndConditional, &CheatList::Loop,
|
||||
&CheatList::LoadImmediate, &CheatList::LoadIndexed,
|
||||
&CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
|
||||
&CheatList::BeginConditionalInput,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u8>(cheat.type.Value());
|
||||
ASSERT(index < sizeof(cheat_operation_functions));
|
||||
const auto op = cheat_operation_functions[index];
|
||||
(this->*op)(cheat);
|
||||
}
|
||||
|
||||
void CheatList::ExecuteBlock(const Block& block) {
|
||||
encountered_loops.clear();
|
||||
|
||||
ProcessBlockPairs(block);
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
current_index = i;
|
||||
ExecuteSingleCheat(block[i]);
|
||||
i = current_index;
|
||||
}
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const {
|
||||
return {system, std::move(master), std::move(standard)};
|
||||
}
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
|
||||
std::stringstream ss;
|
||||
ss.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(ss, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
CheatList::ProgramSegment master_list;
|
||||
CheatList::ProgramSegment standard_list;
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
const auto master = line[0] == '{';
|
||||
const auto begin = master ? line.find('{') : line.find('[');
|
||||
const auto end = master ? line.rfind('}') : line.rfind(']');
|
||||
|
||||
ASSERT(begin != std::string::npos && end != std::string::npos);
|
||||
|
||||
const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
|
||||
CheatList::Block block{};
|
||||
|
||||
while (i < lines.size() - 1) {
|
||||
line = lines[++i];
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.size() < 8)
|
||||
continue;
|
||||
|
||||
Cheat out{};
|
||||
out.raw = ParseSingleLineCheat(line);
|
||||
block.push_back(out);
|
||||
}
|
||||
|
||||
(master ? master_list : standard_list).emplace_back(patch_name, block);
|
||||
}
|
||||
}
|
||||
|
||||
return MakeCheatList(system, master_list, standard_list);
|
||||
}
|
||||
|
||||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
|
||||
std::array<u8, 16> out{};
|
||||
|
||||
if (line.size() < 8)
|
||||
return out;
|
||||
|
||||
const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
|
||||
std::memcpy(out.data(), word1.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 17 || line[8] != ' ')
|
||||
return out;
|
||||
|
||||
const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
|
||||
std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 26 || line[17] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
|
||||
type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
|
||||
std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
|
||||
std::memset(out.data() + 4, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
|
||||
std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 35 || line[26] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
|
||||
std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
|
||||
std::memset(out.data() + 8, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
|
||||
std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
u64 MemoryReadImpl(u32 width, VAddr addr) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return Memory::Read8(addr);
|
||||
case 2:
|
||||
return Memory::Read16(addr);
|
||||
case 4:
|
||||
return Memory::Read32(addr);
|
||||
case 8:
|
||||
return Memory::Read64(addr);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
Memory::Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
Memory::Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
Memory::Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
Memory::Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end)
|
||||
: cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
|
||||
event = core_timing.RegisterEvent(
|
||||
"CheatEngine::FrameCallback::" + build_id,
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
|
||||
|
||||
const auto& vm_manager = system.CurrentProcess()->VMManager();
|
||||
for (auto& list : this->cheats) {
|
||||
list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
|
||||
code_region_end, vm_manager.GetHeapRegionEndAddress(),
|
||||
&MemoryWriteImpl, &MemoryReadImpl);
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
|
||||
for (auto& list : cheats) {
|
||||
list.Execute();
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -1,234 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class CodeType : u32 {
|
||||
// 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Writes a T sized value Y to the address A added to the value of register R in memory domain M
|
||||
WriteImmediate = 0,
|
||||
|
||||
// 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Compares the T sized value Y to the value at address A in memory domain M using the
|
||||
// conditional function C. If success, continues execution. If failure, jumps to the matching
|
||||
// EndConditional statement.
|
||||
Conditional = 1,
|
||||
|
||||
// 20000000
|
||||
// Terminates a Conditional or ConditionalInput block.
|
||||
EndConditional = 2,
|
||||
|
||||
// 300R0000 VVVVVVVV
|
||||
// Starts looping V times, storing the current count in register R.
|
||||
// Loop block is terminated with a matching 310R0000.
|
||||
Loop = 3,
|
||||
|
||||
// 400R0000 VVVVVVVV VVVVVVVV
|
||||
// Sets the value of register R to the value V.
|
||||
LoadImmediate = 4,
|
||||
|
||||
// 5TMRI0AA AAAAAAAA
|
||||
// Sets the value of register R to the value of width T at address A in memory domain M, with
|
||||
// the current value of R added to the address if I == 1.
|
||||
LoadIndexed = 5,
|
||||
|
||||
// 6T0RIFG0 VVVVVVVV VVVVVVVV
|
||||
// Writes the value V of width T to the memory address stored in register R. Adds the value of
|
||||
// register G to the final calculation if F is nonzero. Increments the value of register R by T
|
||||
// after operation if I is nonzero.
|
||||
StoreIndexed = 6,
|
||||
|
||||
// 7T0RA000 VVVVVVVV
|
||||
// Performs the arithmetic operation A on the value in register R and the value V of width T,
|
||||
// storing the result in register R.
|
||||
RegisterArithmetic = 7,
|
||||
|
||||
// 8KKKKKKK
|
||||
// Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
|
||||
// execution continues. If none are, execution skips to the next EndConditional command.
|
||||
ConditionalInput = 8,
|
||||
};
|
||||
|
||||
enum class MemoryType : u32 {
|
||||
// Addressed relative to start of main NSO
|
||||
MainNSO = 0,
|
||||
|
||||
// Addressed relative to start of heap
|
||||
Heap = 1,
|
||||
};
|
||||
|
||||
enum class ArithmeticOp : u32 {
|
||||
Add = 0,
|
||||
Sub = 1,
|
||||
Mult = 2,
|
||||
LShift = 3,
|
||||
RShift = 4,
|
||||
};
|
||||
|
||||
enum class ComparisonOp : u32 {
|
||||
GreaterThan = 1,
|
||||
GreaterThanEqual = 2,
|
||||
LessThan = 3,
|
||||
LessThanEqual = 4,
|
||||
Equal = 5,
|
||||
Inequal = 6,
|
||||
};
|
||||
|
||||
union Cheat {
|
||||
std::array<u8, 16> raw;
|
||||
|
||||
BitField<4, 4, CodeType> type;
|
||||
BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
|
||||
BitField<0, 4, u32> end_of_loop;
|
||||
BitField<12, 4, MemoryType> memory_type;
|
||||
BitField<8, 4, u32> register_3;
|
||||
BitField<8, 4, ComparisonOp> comparison_op;
|
||||
BitField<20, 4, u32> load_from_register;
|
||||
BitField<20, 4, u32> increment_register;
|
||||
BitField<20, 4, ArithmeticOp> arithmetic_op;
|
||||
BitField<16, 4, u32> add_additional_register;
|
||||
BitField<28, 4, u32> register_6;
|
||||
|
||||
u64 Address() const;
|
||||
u64 ValueWidth(u64 offset) const;
|
||||
u64 Value(u64 offset, u64 width) const;
|
||||
u32 KeypadValue() const;
|
||||
};
|
||||
|
||||
class CheatParser;
|
||||
|
||||
// Represents a full collection of cheats for a game. The Execute function should be called every
|
||||
// interval that all cheats should be executed. Clients should not directly instantiate this class
|
||||
// (hence private constructor), they should instead receive an instance from CheatParser, which
|
||||
// guarantees the list is always in an acceptable state.
|
||||
class CheatList {
|
||||
public:
|
||||
friend class CheatParser;
|
||||
|
||||
using Block = std::vector<Cheat>;
|
||||
using ProgramSegment = std::vector<std::pair<std::string, Block>>;
|
||||
|
||||
// (width in bytes, address, value)
|
||||
using MemoryWriter = void (*)(u32, VAddr, u64);
|
||||
// (width in bytes, address) -> value
|
||||
using MemoryReader = u64 (*)(u32, VAddr);
|
||||
|
||||
void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
|
||||
MemoryWriter writer, MemoryReader reader);
|
||||
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
|
||||
|
||||
void ProcessBlockPairs(const Block& block);
|
||||
void ExecuteSingleCheat(const Cheat& cheat);
|
||||
|
||||
void ExecuteBlock(const Block& block);
|
||||
|
||||
bool EvaluateConditional(const Cheat& cheat) const;
|
||||
|
||||
// Individual cheat operations
|
||||
void WriteImmediate(const Cheat& cheat);
|
||||
void BeginConditional(const Cheat& cheat);
|
||||
void EndConditional(const Cheat& cheat);
|
||||
void Loop(const Cheat& cheat);
|
||||
void LoadImmediate(const Cheat& cheat);
|
||||
void LoadIndexed(const Cheat& cheat);
|
||||
void StoreIndexed(const Cheat& cheat);
|
||||
void RegisterArithmetic(const Cheat& cheat);
|
||||
void BeginConditionalInput(const Cheat& cheat);
|
||||
|
||||
VAddr SanitizeAddress(VAddr in) const;
|
||||
|
||||
// Master Codes are defined as codes that cannot be disabled and are run prior to all
|
||||
// others.
|
||||
ProgramSegment master_list;
|
||||
// All other codes
|
||||
ProgramSegment standard_list;
|
||||
|
||||
bool in_standard = false;
|
||||
|
||||
// 16 (0x0-0xF) scratch registers that can be used by cheats
|
||||
std::array<u64, 16> scratch{};
|
||||
|
||||
MemoryWriter writer = nullptr;
|
||||
MemoryReader reader = nullptr;
|
||||
|
||||
u64 main_region_begin{};
|
||||
u64 heap_region_begin{};
|
||||
u64 main_region_end{};
|
||||
u64 heap_region_end{};
|
||||
|
||||
u64 current_block{};
|
||||
// The current index of the cheat within the current Block
|
||||
u64 current_index{};
|
||||
|
||||
// The 'stack' of the program. When a conditional or loop statement is encountered, its index is
|
||||
// pushed onto this queue. When a end block is encountered, the condition is checked.
|
||||
std::map<u64, u64> block_pairs;
|
||||
|
||||
std::set<u64> encountered_loops;
|
||||
|
||||
const Core::System* system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
|
||||
|
||||
protected:
|
||||
CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
|
||||
|
||||
private:
|
||||
std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
~CheatEngine();
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, s64 cycles_late);
|
||||
|
||||
std::vector<CheatList> cheats;
|
||||
|
||||
Core::Timing::EventType* event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -528,6 +528,14 @@ u64 NCA::GetTitleId() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
std::array<u8, 16> NCA::GetRightsId() const {
|
||||
return header.rights_id;
|
||||
}
|
||||
|
||||
u32 NCA::GetSDKVersion() const {
|
||||
return header.sdk_version;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ public:
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
std::array<u8, 0x10> GetRightsId() const;
|
||||
u32 GetSDKVersion() const;
|
||||
bool IsUpdate() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -63,7 +64,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
|
||||
if (Settings::values.dump_exefs) {
|
||||
LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
|
||||
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
|
||||
const auto dump_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
|
||||
VfsRawCopyD(exefs, exefs_dir);
|
||||
@@ -88,7 +90,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
}
|
||||
|
||||
// LayeredExeFS
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir != nullptr && load_dir->GetSize() > 0) {
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(
|
||||
@@ -174,7 +177,8 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
if (Settings::values.dump_nso) {
|
||||
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
|
||||
title_id);
|
||||
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
|
||||
const auto dump_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
|
||||
@@ -186,7 +190,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return nso;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
@@ -224,7 +234,13 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
@@ -232,9 +248,10 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
|
||||
const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
namespace {
|
||||
std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||
const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
@@ -252,31 +269,39 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextCheatParser parser;
|
||||
return parser.Parse(system, data);
|
||||
Memory::TextCheatParser parser;
|
||||
return parser.Parse(
|
||||
system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 32>& build_id_) const {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||
const Core::System& system, const std::array<u8, 32>& build_id_) const {
|
||||
const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<CheatList> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
std::vector<Memory::CheatEntry> out;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto cheats_dir = subdir->GetSubdirectory("cheats");
|
||||
if (cheats_dir != nullptr) {
|
||||
auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
|
||||
if (res.has_value()) {
|
||||
out.push_back(std::move(*res));
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
|
||||
if (res.has_value())
|
||||
out.push_back(std::move(*res));
|
||||
if (res.has_value()) {
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +309,8 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto load_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||
load_dir == nullptr || load_dir->GetSize() <= 0) {
|
||||
return;
|
||||
@@ -393,6 +419,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
||||
VirtualFile update_raw) const {
|
||||
if (title_id == 0)
|
||||
return {};
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
@@ -423,7 +451,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
const auto mod_dir =
|
||||
Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -51,8 +51,8 @@ public:
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
std::vector<CheatList> CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 0x20>& build_id) const;
|
||||
std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
@@ -48,18 +49,21 @@ static bool FollowsTwoDigitDirFormat(std::string_view name) {
|
||||
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 const std::regex nca_id_cnmt_regex(
|
||||
"[0-9A-F]{32}\\.cnmt.nca", std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||
return (name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex)) ||
|
||||
(name.size() == 41 && std::regex_match(name.begin(), name.end(), nca_id_cnmt_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", Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
bool within_two_digit, bool cnmt_suffix) {
|
||||
if (!within_two_digit)
|
||||
return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca",
|
||||
Common::HexToString(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],
|
||||
return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0],
|
||||
Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
@@ -127,6 +131,156 @@ std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
|
||||
return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {}
|
||||
|
||||
bool PlaceholderCache::Create(const NcaID& id, u64 size) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
if (dir->GetFileRelative(path) != nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
|
||||
const auto dirname = fmt::format("000000{:02X}", hash[0]);
|
||||
|
||||
const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
|
||||
|
||||
if (dir2 == nullptr)
|
||||
return false;
|
||||
|
||||
const auto file = dir2->CreateFile(fmt::format("{}.nca", Common::HexToString(id, false)));
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
return file->Resize(size);
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Delete(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
if (dir->GetFileRelative(path) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(id.data(), id.size(), hash.data(), 0);
|
||||
const auto dirname = fmt::format("000000{:02X}", hash[0]);
|
||||
|
||||
const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname);
|
||||
|
||||
const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false)));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Exists(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
return dir->GetFileRelative(path) != nullptr;
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
return file->WriteBytes(data, offset) == data.size();
|
||||
}
|
||||
|
||||
bool PlaceholderCache::Register(RegisteredCache* cache, const NcaID& placeholder,
|
||||
const NcaID& install) const {
|
||||
const auto path = GetRelativePathFromNcaID(placeholder, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
const auto res = cache->RawInstallNCA(NCA{file}, &VfsRawCopy, false, install);
|
||||
|
||||
if (res != InstallResult::Success)
|
||||
return false;
|
||||
|
||||
return Delete(placeholder);
|
||||
}
|
||||
|
||||
bool PlaceholderCache::CleanAll() const {
|
||||
return dir->GetParentDirectory()->CleanSubdirectoryRecursive(dir->GetName());
|
||||
}
|
||||
|
||||
std::optional<std::array<u8, 0x10>> PlaceholderCache::GetRightsID(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
NCA nca{file};
|
||||
|
||||
if (nca.GetStatus() != Loader::ResultStatus::Success &&
|
||||
nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto rights_id = nca.GetRightsId();
|
||||
if (rights_id == NcaID{})
|
||||
return std::nullopt;
|
||||
|
||||
return rights_id;
|
||||
}
|
||||
|
||||
u64 PlaceholderCache::Size(const NcaID& id) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return 0;
|
||||
|
||||
return file->GetSize();
|
||||
}
|
||||
|
||||
bool PlaceholderCache::SetSize(const NcaID& id, u64 new_size) const {
|
||||
const auto path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
return file->Resize(new_size);
|
||||
}
|
||||
|
||||
std::vector<NcaID> PlaceholderCache::List() const {
|
||||
std::vector<NcaID> out;
|
||||
for (const auto& sdir : dir->GetSubdirectories()) {
|
||||
for (const auto& file : sdir->GetFiles()) {
|
||||
const auto name = file->GetName();
|
||||
if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' &&
|
||||
name[35] == 'a') {
|
||||
out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
NcaID PlaceholderCache::Generate() {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
|
||||
|
||||
NcaID out{};
|
||||
|
||||
const auto v1 = distribution(gen);
|
||||
const auto v2 = distribution(gen);
|
||||
std::memcpy(out.data(), &v1, sizeof(u64));
|
||||
std::memcpy(out.data() + sizeof(u64), &v2, sizeof(u64));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
std::string_view path) const {
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
@@ -169,14 +323,18 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
|
||||
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);
|
||||
// Try all five relevant modes of file storage:
|
||||
// (bit 2 = uppercase/lower, bit 1 = within a two-digit dir, bit 0 = .cnmt suffix)
|
||||
// 000: /000000**/{:032X}.nca
|
||||
// 010: /{:032X}.nca
|
||||
// 100: /000000**/{:032x}.nca
|
||||
// 110: /{:032x}.nca
|
||||
// 111: /{:032x}.cnmt.nca
|
||||
for (u8 i = 0; i < 8; ++i) {
|
||||
if ((i % 2) == 1 && i != 7)
|
||||
continue;
|
||||
const auto path =
|
||||
GetRelativePathFromNcaID(id, (i & 0b100) == 0, (i & 0b010) == 0, (i & 0b001) == 0b001);
|
||||
file = OpenFileOrDirectoryConcat(dir, path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
@@ -472,7 +630,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti
|
||||
memcpy(id.data(), hash.data(), 16);
|
||||
}
|
||||
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true);
|
||||
std::string path = GetRelativePathFromNcaID(id, false, true, false);
|
||||
|
||||
if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
|
||||
LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
|
||||
|
||||
@@ -25,6 +25,8 @@ enum class NCAContentType : u8;
|
||||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
struct MetaRecord;
|
||||
class RegisteredCache;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
@@ -89,6 +91,27 @@ protected:
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
class PlaceholderCache {
|
||||
public:
|
||||
explicit PlaceholderCache(VirtualDir dir);
|
||||
|
||||
bool Create(const NcaID& id, u64 size) const;
|
||||
bool Delete(const NcaID& id) const;
|
||||
bool Exists(const NcaID& id) const;
|
||||
bool Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const;
|
||||
bool Register(RegisteredCache* cache, const NcaID& placeholder, const NcaID& install) const;
|
||||
bool CleanAll() const;
|
||||
std::optional<std::array<u8, 0x10>> GetRightsID(const NcaID& id) const;
|
||||
u64 Size(const NcaID& id) const;
|
||||
bool SetSize(const NcaID& id, u64 new_size) const;
|
||||
std::vector<NcaID> List() const;
|
||||
|
||||
static NcaID Generate();
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
};
|
||||
|
||||
/*
|
||||
* A class that catalogues NCAs in the registered directory structure.
|
||||
* Nintendo's registered format follows this structure:
|
||||
@@ -103,6 +126,8 @@ protected:
|
||||
* when 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache : public ContentProvider {
|
||||
friend class PlaceholderCache;
|
||||
|
||||
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
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -34,7 +35,7 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
|
||||
this->update_raw = std::move(update_raw);
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const {
|
||||
if (!updatable)
|
||||
return MakeResult<VirtualFile>(file);
|
||||
|
||||
@@ -43,7 +44,8 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
|
||||
ContentRecordType type) const {
|
||||
std::shared_ptr<NCA> res;
|
||||
|
||||
switch (storage) {
|
||||
@@ -51,13 +53,17 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
|
||||
res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandSystem:
|
||||
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
res =
|
||||
Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry(
|
||||
title_id, type);
|
||||
break;
|
||||
case StorageId::NandUser:
|
||||
res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry(
|
||||
title_id, type);
|
||||
break;
|
||||
case StorageId::SdCard:
|
||||
res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type);
|
||||
res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry(
|
||||
title_id, type);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
|
||||
|
||||
@@ -33,8 +33,8 @@ public:
|
||||
~RomFSFactory();
|
||||
|
||||
void SetPackedUpdate(VirtualFile update_raw);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess();
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess() const;
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
|
||||
@@ -15,22 +15,8 @@ namespace FileSys {
|
||||
|
||||
constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
|
||||
|
||||
std::string SaveDataDescriptor::DebugInfo() const {
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, "
|
||||
"rank={}, index={}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
|
||||
static_cast<u8>(rank), index);
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) {
|
||||
namespace {
|
||||
void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) {
|
||||
if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
|
||||
if (meta.zero_1 != 0) {
|
||||
LOG_WARNING(Service_FS,
|
||||
@@ -65,23 +51,51 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDat
|
||||
"non-zero ({:016X}{:016X})",
|
||||
meta.user_id[1], meta.user_id[0]);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::string save_directory =
|
||||
std::string SaveDataDescriptor::DebugInfo() const {
|
||||
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, "
|
||||
"save_id={:016X}, "
|
||||
"rank={}, index={}]",
|
||||
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id,
|
||||
static_cast<u8>(rank), index);
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
|
||||
const SaveDataDescriptor& meta) const {
|
||||
PrintSaveDataDescriptorWarnings(meta);
|
||||
|
||||
const auto save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
// TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
|
||||
// But, user_ids don't match so this works for now.
|
||||
auto out = dir->CreateDirectoryRelative(save_directory);
|
||||
|
||||
// Return an error if the save data doesn't actually exist.
|
||||
if (out == nullptr) {
|
||||
// TODO(DarkLordZach): Find out correct error code.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return MakeResult<VirtualDir>(std::move(out));
|
||||
}
|
||||
|
||||
ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
|
||||
const SaveDataDescriptor& meta) const {
|
||||
|
||||
const auto save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr) {
|
||||
// TODO(bunnei): This is a work-around to always create a save data directory if it does not
|
||||
// already exist. This is a hack, as we do not understand yet how this works on hardware.
|
||||
// Without a save data directory, many games will assert on boot. This should not have any
|
||||
// bad side-effects.
|
||||
out = dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
// Return an error if the save data doesn't actually exist.
|
||||
if (out == nullptr) {
|
||||
// TODO(Subv): Find out correct error code.
|
||||
@@ -152,7 +166,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
|
||||
}
|
||||
|
||||
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
|
||||
SaveDataSize new_value) {
|
||||
SaveDataSize new_value) const {
|
||||
const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
|
||||
const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ public:
|
||||
explicit SaveDataFactory(VirtualDir dir);
|
||||
~SaveDataFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta);
|
||||
ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
|
||||
ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const;
|
||||
|
||||
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
|
||||
|
||||
@@ -73,7 +74,8 @@ public:
|
||||
u128 user_id, u64 save_id);
|
||||
|
||||
SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const;
|
||||
void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value);
|
||||
void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
|
||||
SaveDataSize new_value) const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -14,16 +15,38 @@ SDMCFactory::SDMCFactory(VirtualDir dir_)
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
|
||||
[](const VirtualFile& file, const NcaID& id) {
|
||||
return NAX{file, id}.GetDecrypted();
|
||||
})) {}
|
||||
})),
|
||||
placeholder(std::make_unique<PlaceholderCache>(
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {}
|
||||
|
||||
SDMCFactory::~SDMCFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SDMCFactory::Open() {
|
||||
ResultVal<VirtualDir> SDMCFactory::Open() const {
|
||||
return MakeResult<VirtualDir>(dir);
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents");
|
||||
}
|
||||
|
||||
RegisteredCache* SDMCFactory::GetSDMCContents() const {
|
||||
return contents.get();
|
||||
}
|
||||
|
||||
PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
|
||||
return placeholder.get();
|
||||
}
|
||||
|
||||
VirtualDir SDMCFactory::GetImageDirectory() const {
|
||||
return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album");
|
||||
}
|
||||
|
||||
u64 SDMCFactory::GetSDMCFreeSpace() const {
|
||||
return GetSDMCTotalSpace() - dir->GetSize();
|
||||
}
|
||||
|
||||
u64 SDMCFactory::GetSDMCTotalSpace() const {
|
||||
return static_cast<u64>(Settings::values.sdmc_size);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
namespace FileSys {
|
||||
|
||||
class RegisteredCache;
|
||||
class PlaceholderCache;
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMCFactory {
|
||||
@@ -18,13 +19,23 @@ public:
|
||||
explicit SDMCFactory(VirtualDir dir);
|
||||
~SDMCFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open();
|
||||
ResultVal<VirtualDir> Open() const;
|
||||
|
||||
VirtualDir GetSDMCContentDirectory() const;
|
||||
|
||||
RegisteredCache* GetSDMCContents() const;
|
||||
PlaceholderCache* GetSDMCPlaceholder() const;
|
||||
|
||||
VirtualDir GetImageDirectory() const;
|
||||
|
||||
u64 GetSDMCFreeSpace() const;
|
||||
u64 GetSDMCTotalSpace() const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
std::unique_ptr<RegisteredCache> contents;
|
||||
std::unique_ptr<PlaceholderCache> placeholder;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@@ -78,6 +79,10 @@ Loader::ResultStatus NSP::GetStatus() const {
|
||||
}
|
||||
|
||||
Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
|
||||
if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) {
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
const auto iter = program_status.find(title_id);
|
||||
if (iter == program_status.end())
|
||||
return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
|
||||
@@ -85,12 +90,29 @@ Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
|
||||
}
|
||||
|
||||
u64 NSP::GetFirstTitleID() const {
|
||||
if (IsExtractedType()) {
|
||||
return GetProgramTitleID();
|
||||
}
|
||||
|
||||
if (program_status.empty())
|
||||
return 0;
|
||||
return program_status.begin()->first;
|
||||
}
|
||||
|
||||
u64 NSP::GetProgramTitleID() const {
|
||||
if (IsExtractedType()) {
|
||||
if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ProgramMetadata meta;
|
||||
if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) {
|
||||
return meta.GetTitleID();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const auto out = GetFirstTitleID();
|
||||
if ((out & 0x800) == 0)
|
||||
return out;
|
||||
@@ -102,6 +124,10 @@ u64 NSP::GetProgramTitleID() const {
|
||||
}
|
||||
|
||||
std::vector<u64> NSP::GetTitleIDs() const {
|
||||
if (IsExtractedType()) {
|
||||
return {GetProgramTitleID()};
|
||||
}
|
||||
|
||||
std::vector<u64> out;
|
||||
out.reserve(ncas.size());
|
||||
for (const auto& kv : ncas)
|
||||
@@ -222,7 +248,8 @@ void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
|
||||
|
||||
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
if (outer_file->GetName().size() < 9 ||
|
||||
outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1143,13 +1143,21 @@ void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(
|
||||
|
||||
void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u128 uid = rp.PopRaw<u128>(); // What does this do?
|
||||
LOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]);
|
||||
u128 user_id = rp.PopRaw<u128>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]);
|
||||
|
||||
FileSys::SaveDataDescriptor descriptor{};
|
||||
descriptor.title_id = Core::CurrentProcess()->GetTitleID();
|
||||
descriptor.user_id = user_id;
|
||||
descriptor.type = FileSys::SaveDataType::SaveData;
|
||||
const auto res = system.GetFileSystemController().CreateSaveData(
|
||||
FileSys::SaveDataSpaceId::NandUser, descriptor);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(res.Code());
|
||||
rb.Push<u64>(0);
|
||||
} // namespace Service::AM
|
||||
}
|
||||
|
||||
void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) {
|
||||
// Takes an input u32 Result, no output.
|
||||
@@ -1261,8 +1269,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) {
|
||||
"new_journal={:016X}",
|
||||
static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
|
||||
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size});
|
||||
system.GetFileSystemController().WriteSaveDataSize(
|
||||
type, system.CurrentProcess()->GetTitleID(), user_id, {new_normal_size, new_journal_size});
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -1281,8 +1289,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type),
|
||||
user_id[1], user_id[0]);
|
||||
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id);
|
||||
const auto size = system.GetFileSystemController().ReadSaveDataSize(
|
||||
type, system.CurrentProcess()->GetTitleID(), user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
@@ -25,14 +26,10 @@
|
||||
#include "core/hle/service/filesystem/fsp_pr.h"
|
||||
#include "core/hle/service/filesystem/fsp_srv.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
// Size of emulated sd card free space, reported in bytes.
|
||||
// Just using 32GB because thats reasonable
|
||||
// TODO(DarkLordZach): Eventually make this configurable in settings.
|
||||
constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000;
|
||||
|
||||
// A default size for normal/journal save data size if application control metadata cannot be found.
|
||||
// This should be large enough to satisfy even the most extreme requirements (~4.2GB)
|
||||
constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000;
|
||||
@@ -226,13 +223,6 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s
|
||||
return MakeResult(dir);
|
||||
}
|
||||
|
||||
u64 VfsDirectoryServiceWrapper::GetFreeSpaceSize() const {
|
||||
if (backing->IsWritable())
|
||||
return EMULATED_SD_REPORTED_SIZE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
|
||||
const std::string& path_) const {
|
||||
std::string path(FileUtil::SanitizePath(path_));
|
||||
@@ -251,44 +241,39 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
|
||||
return FileSys::ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of registered file systems, identified by type. Once an file system is registered here, it
|
||||
* is never removed until UnregisterFileSystems is called.
|
||||
*/
|
||||
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;
|
||||
FileSystemController::FileSystemController() = default;
|
||||
|
||||
ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
|
||||
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
|
||||
FileSystemController::~FileSystemController() = default;
|
||||
|
||||
ResultCode FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
|
||||
romfs_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registered RomFS");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
|
||||
ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second save data");
|
||||
ResultCode FileSystemController::RegisterSaveData(
|
||||
std::unique_ptr<FileSys::SaveDataFactory>&& factory) {
|
||||
ASSERT_MSG(save_data_factory == nullptr, "Tried to register a second save data");
|
||||
save_data_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registered save data");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
|
||||
ResultCode FileSystemController::RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
|
||||
ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC");
|
||||
sdmc_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registered SDMC");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
|
||||
ResultCode FileSystemController::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, "Registered BIS");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void SetPackedUpdate(FileSys::VirtualFile update_raw) {
|
||||
void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) {
|
||||
LOG_TRACE(Service_FS, "Setting packed update for romfs");
|
||||
|
||||
if (romfs_factory == nullptr)
|
||||
@@ -297,7 +282,7 @@ void SetPackedUpdate(FileSys::VirtualFile update_raw) {
|
||||
romfs_factory->SetPackedUpdate(std::move(update_raw));
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
|
||||
ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for current process");
|
||||
|
||||
if (romfs_factory == nullptr) {
|
||||
@@ -308,8 +293,8 @@ ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
|
||||
return romfs_factory->OpenCurrentProcess();
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type) {
|
||||
ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
|
||||
u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
|
||||
title_id, static_cast<u8>(storage_id), static_cast<u8>(type));
|
||||
|
||||
@@ -321,8 +306,20 @@ ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId stora
|
||||
return romfs_factory->Open(title_id, storage_id, type);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::SaveDataDescriptor& descriptor) {
|
||||
ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
|
||||
FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const {
|
||||
LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}",
|
||||
static_cast<u8>(space), save_struct.DebugInfo());
|
||||
|
||||
if (save_data_factory == nullptr) {
|
||||
return FileSys::ERROR_ENTITY_NOT_FOUND;
|
||||
}
|
||||
|
||||
return save_data_factory->Create(space, save_struct);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
|
||||
FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& descriptor) const {
|
||||
LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
|
||||
static_cast<u8>(space), descriptor.DebugInfo());
|
||||
|
||||
@@ -333,7 +330,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
return save_data_factory->Open(space, descriptor);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) {
|
||||
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
|
||||
FileSys::SaveDataSpaceId space) const {
|
||||
LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space));
|
||||
|
||||
if (save_data_factory == nullptr) {
|
||||
@@ -343,7 +341,7 @@ ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space)
|
||||
return MakeResult(save_data_factory->GetSaveDataSpaceDirectory(space));
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC");
|
||||
|
||||
if (sdmc_factory == nullptr) {
|
||||
@@ -353,7 +351,92 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
return sdmc_factory->Open();
|
||||
}
|
||||
|
||||
FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id) {
|
||||
ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
|
||||
FileSys::BisPartitionId id) const {
|
||||
LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", static_cast<u32>(id));
|
||||
|
||||
if (bis_factory == nullptr) {
|
||||
return FileSys::ERROR_ENTITY_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto part = bis_factory->OpenPartition(id);
|
||||
if (part == nullptr) {
|
||||
return FileSys::ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return MakeResult<FileSys::VirtualDir>(std::move(part));
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
|
||||
FileSys::BisPartitionId id) const {
|
||||
LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", static_cast<u32>(id));
|
||||
|
||||
if (bis_factory == nullptr) {
|
||||
return FileSys::ERROR_ENTITY_NOT_FOUND;
|
||||
}
|
||||
|
||||
auto part = bis_factory->OpenPartitionStorage(id);
|
||||
if (part == nullptr) {
|
||||
return FileSys::ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
return MakeResult<FileSys::VirtualFile>(std::move(part));
|
||||
}
|
||||
|
||||
u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const {
|
||||
switch (id) {
|
||||
case FileSys::StorageId::None:
|
||||
case FileSys::StorageId::GameCard:
|
||||
return 0;
|
||||
case FileSys::StorageId::SdCard:
|
||||
if (sdmc_factory == nullptr)
|
||||
return 0;
|
||||
return sdmc_factory->GetSDMCFreeSpace();
|
||||
case FileSys::StorageId::Host:
|
||||
if (bis_factory == nullptr)
|
||||
return 0;
|
||||
return bis_factory->GetSystemNANDFreeSpace() + bis_factory->GetUserNANDFreeSpace();
|
||||
case FileSys::StorageId::NandSystem:
|
||||
if (bis_factory == nullptr)
|
||||
return 0;
|
||||
return bis_factory->GetSystemNANDFreeSpace();
|
||||
case FileSys::StorageId::NandUser:
|
||||
if (bis_factory == nullptr)
|
||||
return 0;
|
||||
return bis_factory->GetUserNANDFreeSpace();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 FileSystemController::GetTotalSpaceSize(FileSys::StorageId id) const {
|
||||
switch (id) {
|
||||
case FileSys::StorageId::None:
|
||||
case FileSys::StorageId::GameCard:
|
||||
return 0;
|
||||
case FileSys::StorageId::SdCard:
|
||||
if (sdmc_factory == nullptr)
|
||||
return 0;
|
||||
return sdmc_factory->GetSDMCTotalSpace();
|
||||
case FileSys::StorageId::Host:
|
||||
if (bis_factory == nullptr)
|
||||
return 0;
|
||||
return bis_factory->GetFullNANDTotalSpace();
|
||||
case FileSys::StorageId::NandSystem:
|
||||
if (bis_factory == nullptr)
|
||||
return 0;
|
||||
return bis_factory->GetSystemNANDTotalSpace();
|
||||
case FileSys::StorageId::NandUser:
|
||||
if (bis_factory == nullptr)
|
||||
return 0;
|
||||
return bis_factory->GetUserNANDTotalSpace();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataType type,
|
||||
u64 title_id, u128 user_id) const {
|
||||
if (save_data_factory == nullptr) {
|
||||
return {0, 0};
|
||||
}
|
||||
@@ -385,13 +468,32 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
|
||||
return value;
|
||||
}
|
||||
|
||||
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
FileSys::SaveDataSize new_value) {
|
||||
void FileSystemController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
FileSys::SaveDataSize new_value) const {
|
||||
if (save_data_factory != nullptr)
|
||||
save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents() {
|
||||
void FileSystemController::SetGameCard(FileSys::VirtualFile file) {
|
||||
gamecard = std::make_unique<FileSys::XCI>(file);
|
||||
const auto dir = gamecard->ConcatenatedPseudoDirectory();
|
||||
gamecard_registered = std::make_unique<FileSys::RegisteredCache>(dir);
|
||||
gamecard_placeholder = std::make_unique<FileSys::PlaceholderCache>(dir);
|
||||
}
|
||||
|
||||
FileSys::XCI* FileSystemController::GetGameCard() const {
|
||||
return gamecard.get();
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* FileSystemController::GetGameCardContents() const {
|
||||
return gamecard_registered.get();
|
||||
}
|
||||
|
||||
FileSys::PlaceholderCache* FileSystemController::GetGameCardPlaceholder() const {
|
||||
return gamecard_placeholder.get();
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* FileSystemController::GetSystemNANDContents() const {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Contents");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
@@ -400,7 +502,7 @@ FileSys::RegisteredCache* GetSystemNANDContents() {
|
||||
return bis_factory->GetSystemNANDContents();
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* GetUserNANDContents() {
|
||||
FileSys::RegisteredCache* FileSystemController::GetUserNANDContents() const {
|
||||
LOG_TRACE(Service_FS, "Opening User NAND Contents");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
@@ -409,7 +511,7 @@ FileSys::RegisteredCache* GetUserNANDContents() {
|
||||
return bis_factory->GetUserNANDContents();
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* GetSDMCContents() {
|
||||
FileSys::RegisteredCache* FileSystemController::GetSDMCContents() const {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC Contents");
|
||||
|
||||
if (sdmc_factory == nullptr)
|
||||
@@ -418,7 +520,143 @@ FileSys::RegisteredCache* GetSDMCContents() {
|
||||
return sdmc_factory->GetSDMCContents();
|
||||
}
|
||||
|
||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
|
||||
FileSys::PlaceholderCache* FileSystemController::GetSystemNANDPlaceholder() const {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Placeholder");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetSystemNANDPlaceholder();
|
||||
}
|
||||
|
||||
FileSys::PlaceholderCache* FileSystemController::GetUserNANDPlaceholder() const {
|
||||
LOG_TRACE(Service_FS, "Opening User NAND Placeholder");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetUserNANDPlaceholder();
|
||||
}
|
||||
|
||||
FileSys::PlaceholderCache* FileSystemController::GetSDMCPlaceholder() const {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC Placeholder");
|
||||
|
||||
if (sdmc_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return sdmc_factory->GetSDMCPlaceholder();
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* FileSystemController::GetRegisteredCacheForStorage(
|
||||
FileSys::StorageId id) const {
|
||||
switch (id) {
|
||||
case FileSys::StorageId::None:
|
||||
case FileSys::StorageId::Host:
|
||||
UNIMPLEMENTED();
|
||||
return nullptr;
|
||||
case FileSys::StorageId::GameCard:
|
||||
return GetGameCardContents();
|
||||
case FileSys::StorageId::NandSystem:
|
||||
return GetSystemNANDContents();
|
||||
case FileSys::StorageId::NandUser:
|
||||
return GetUserNANDContents();
|
||||
case FileSys::StorageId::SdCard:
|
||||
return GetSDMCContents();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSys::PlaceholderCache* FileSystemController::GetPlaceholderCacheForStorage(
|
||||
FileSys::StorageId id) const {
|
||||
switch (id) {
|
||||
case FileSys::StorageId::None:
|
||||
case FileSys::StorageId::Host:
|
||||
UNIMPLEMENTED();
|
||||
return nullptr;
|
||||
case FileSys::StorageId::GameCard:
|
||||
return GetGameCardPlaceholder();
|
||||
case FileSys::StorageId::NandSystem:
|
||||
return GetSystemNANDPlaceholder();
|
||||
case FileSys::StorageId::NandUser:
|
||||
return GetUserNANDPlaceholder();
|
||||
case FileSys::StorageId::SdCard:
|
||||
return GetSDMCPlaceholder();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetSystemNANDContentDirectory() const {
|
||||
LOG_TRACE(Service_FS, "Opening system NAND content directory");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetSystemNANDContentDirectory();
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetUserNANDContentDirectory() const {
|
||||
LOG_TRACE(Service_FS, "Opening user NAND content directory");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetUserNANDContentDirectory();
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetSDMCContentDirectory() const {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC content directory");
|
||||
|
||||
if (sdmc_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return sdmc_factory->GetSDMCContentDirectory();
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetNANDImageDirectory() const {
|
||||
LOG_TRACE(Service_FS, "Opening NAND image directory");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetImageDirectory();
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetSDMCImageDirectory() const {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC image directory");
|
||||
|
||||
if (sdmc_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return sdmc_factory->GetImageDirectory();
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetContentDirectory(ContentStorageId id) const {
|
||||
switch (id) {
|
||||
case ContentStorageId::System:
|
||||
return GetSystemNANDContentDirectory();
|
||||
case ContentStorageId::User:
|
||||
return GetUserNANDContentDirectory();
|
||||
case ContentStorageId::SdCard:
|
||||
return GetSDMCContentDirectory();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetImageDirectory(ImageDirectoryId id) const {
|
||||
switch (id) {
|
||||
case ImageDirectoryId::NAND:
|
||||
return GetNANDImageDirectory();
|
||||
case ImageDirectoryId::SdCard:
|
||||
return GetSDMCImageDirectory();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id) const {
|
||||
LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id);
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
@@ -427,7 +665,7 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
|
||||
return bis_factory->GetModificationLoadRoot(title_id);
|
||||
}
|
||||
|
||||
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) {
|
||||
FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const {
|
||||
LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id);
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
@@ -436,7 +674,7 @@ FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) {
|
||||
return bis_factory->GetModificationDumpRoot(title_id);
|
||||
}
|
||||
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (overwrite) {
|
||||
bis_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
@@ -473,11 +711,10 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
}
|
||||
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
romfs_factory = nullptr;
|
||||
CreateFactories(*system.GetFilesystem(), false);
|
||||
std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<FSP_SRV>(system.GetReporter())->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter())
|
||||
->InstallAsService(system.ServiceManager());
|
||||
}
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -14,10 +14,13 @@ namespace FileSys {
|
||||
class BISFactory;
|
||||
class RegisteredCache;
|
||||
class RegisteredCacheUnion;
|
||||
class PlaceholderCache;
|
||||
class RomFSFactory;
|
||||
class SaveDataFactory;
|
||||
class SDMCFactory;
|
||||
class XCI;
|
||||
|
||||
enum class BisPartitionId : u32;
|
||||
enum class ContentRecordType : u8;
|
||||
enum class Mode : u32;
|
||||
enum class SaveDataSpaceId : u8;
|
||||
@@ -36,34 +39,91 @@ class ServiceManager;
|
||||
|
||||
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);
|
||||
enum class ContentStorageId : u32 {
|
||||
System,
|
||||
User,
|
||||
SdCard,
|
||||
};
|
||||
|
||||
void SetPackedUpdate(FileSys::VirtualFile update_raw);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type);
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::SaveDataDescriptor& descriptor);
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space);
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC();
|
||||
enum class ImageDirectoryId : u32 {
|
||||
NAND,
|
||||
SdCard,
|
||||
};
|
||||
|
||||
FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id);
|
||||
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
FileSys::SaveDataSize new_value);
|
||||
class FileSystemController {
|
||||
public:
|
||||
FileSystemController();
|
||||
~FileSystemController();
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents();
|
||||
FileSys::RegisteredCache* GetUserNANDContents();
|
||||
FileSys::RegisteredCache* GetSDMCContents();
|
||||
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);
|
||||
|
||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
|
||||
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id);
|
||||
void SetPackedUpdate(FileSys::VirtualFile update_raw);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type) const;
|
||||
ResultVal<FileSys::VirtualDir> CreateSaveData(
|
||||
FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveData(
|
||||
FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const;
|
||||
ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const;
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC() const;
|
||||
ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const;
|
||||
ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const;
|
||||
|
||||
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
|
||||
// above is called.
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||
u64 GetFreeSpaceSize(FileSys::StorageId id) const;
|
||||
u64 GetTotalSpaceSize(FileSys::StorageId id) const;
|
||||
|
||||
FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
|
||||
u128 user_id) const;
|
||||
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
FileSys::SaveDataSize new_value) const;
|
||||
|
||||
void SetGameCard(FileSys::VirtualFile file);
|
||||
FileSys::XCI* GetGameCard() const;
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents() const;
|
||||
FileSys::RegisteredCache* GetUserNANDContents() const;
|
||||
FileSys::RegisteredCache* GetSDMCContents() const;
|
||||
FileSys::RegisteredCache* GetGameCardContents() const;
|
||||
|
||||
FileSys::PlaceholderCache* GetSystemNANDPlaceholder() const;
|
||||
FileSys::PlaceholderCache* GetUserNANDPlaceholder() const;
|
||||
FileSys::PlaceholderCache* GetSDMCPlaceholder() const;
|
||||
FileSys::PlaceholderCache* GetGameCardPlaceholder() const;
|
||||
|
||||
FileSys::RegisteredCache* GetRegisteredCacheForStorage(FileSys::StorageId id) const;
|
||||
FileSys::PlaceholderCache* GetPlaceholderCacheForStorage(FileSys::StorageId id) const;
|
||||
|
||||
FileSys::VirtualDir GetSystemNANDContentDirectory() const;
|
||||
FileSys::VirtualDir GetUserNANDContentDirectory() const;
|
||||
FileSys::VirtualDir GetSDMCContentDirectory() const;
|
||||
|
||||
FileSys::VirtualDir GetNANDImageDirectory() const;
|
||||
FileSys::VirtualDir GetSDMCImageDirectory() const;
|
||||
|
||||
FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const;
|
||||
FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const;
|
||||
|
||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
|
||||
|
||||
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
|
||||
// above is called.
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
|
||||
std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
|
||||
std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
|
||||
std::unique_ptr<FileSys::BISFactory> bis_factory;
|
||||
|
||||
std::unique_ptr<FileSys::XCI> gamecard;
|
||||
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
|
||||
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
|
||||
};
|
||||
|
||||
void InstallInterfaces(Core::System& system);
|
||||
|
||||
@@ -159,12 +219,6 @@ public:
|
||||
*/
|
||||
ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path);
|
||||
|
||||
/**
|
||||
* Get the free space
|
||||
* @return The number of free bytes in the archive
|
||||
*/
|
||||
u64 GetFreeSpaceSize() const;
|
||||
|
||||
/**
|
||||
* Get the type of the specified path
|
||||
* @return The type of the specified path or error code
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
@@ -30,6 +31,18 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
struct SizeGetter {
|
||||
std::function<u64()> get_free_size;
|
||||
std::function<u64()> get_total_size;
|
||||
|
||||
static SizeGetter FromStorageId(const FileSystemController& fsc, FileSys::StorageId id) {
|
||||
return {
|
||||
[&fsc, id] { return fsc.GetFreeSpaceSize(id); },
|
||||
[&fsc, id] { return fsc.GetTotalSpaceSize(id); },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
enum class FileSystemType : u8 {
|
||||
Invalid0 = 0,
|
||||
Invalid1 = 1,
|
||||
@@ -289,8 +302,8 @@ private:
|
||||
|
||||
class IFileSystem final : public ServiceFramework<IFileSystem> {
|
||||
public:
|
||||
explicit IFileSystem(FileSys::VirtualDir backend)
|
||||
: ServiceFramework("IFileSystem"), backend(std::move(backend)) {
|
||||
explicit IFileSystem(FileSys::VirtualDir backend, SizeGetter size)
|
||||
: ServiceFramework("IFileSystem"), backend(std::move(backend)), size(std::move(size)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IFileSystem::CreateFile, "CreateFile"},
|
||||
{1, &IFileSystem::DeleteFile, "DeleteFile"},
|
||||
@@ -467,14 +480,31 @@ public:
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetFreeSpaceSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(size.get_free_size());
|
||||
}
|
||||
|
||||
void GetTotalSpaceSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(size.get_total_size());
|
||||
}
|
||||
|
||||
private:
|
||||
VfsDirectoryServiceWrapper backend;
|
||||
SizeGetter size;
|
||||
};
|
||||
|
||||
class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
|
||||
public:
|
||||
explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space)
|
||||
: ServiceFramework("ISaveDataInfoReader") {
|
||||
explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc)
|
||||
: ServiceFramework("ISaveDataInfoReader"), fsc(fsc) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
|
||||
};
|
||||
@@ -520,8 +550,13 @@ private:
|
||||
}
|
||||
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
const auto save_root = OpenSaveDataSpace(space);
|
||||
ASSERT(save_root.Succeeded());
|
||||
const auto save_root = fsc.OpenSaveDataSpace(space);
|
||||
|
||||
if (save_root.Failed() || *save_root == nullptr) {
|
||||
LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!",
|
||||
static_cast<u8>(space));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& type : (*save_root)->GetSubdirectories()) {
|
||||
if (type->GetName() == "save") {
|
||||
@@ -610,11 +645,13 @@ private:
|
||||
};
|
||||
static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size.");
|
||||
|
||||
FileSystemController& fsc;
|
||||
std::vector<SaveDataInfo> info;
|
||||
u64 next_entry_index = 0;
|
||||
};
|
||||
|
||||
FSP_SRV::FSP_SRV(const Core::Reporter& reporter) : ServiceFramework("fsp-srv"), reporter(reporter) {
|
||||
FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
: ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "OpenFileSystem"},
|
||||
@@ -754,7 +791,8 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IFileSystem filesystem(OpenSDMC().Unwrap());
|
||||
IFileSystem filesystem(fsc.OpenSDMC().Unwrap(),
|
||||
SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -768,8 +806,10 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
|
||||
u128 uid = rp.PopRaw<u128>();
|
||||
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called save_struct = {}, uid = {:016X}{:016X}",
|
||||
save_struct.DebugInfo(), uid[1], uid[0]);
|
||||
LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(),
|
||||
uid[1], uid[0]);
|
||||
|
||||
fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -786,14 +826,24 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
|
||||
auto dir = OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
|
||||
auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
|
||||
if (dir.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
|
||||
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystem filesystem(std::move(dir.Unwrap()));
|
||||
FileSys::StorageId id;
|
||||
if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) {
|
||||
id = FileSys::StorageId::NandUser;
|
||||
} else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem ||
|
||||
parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) {
|
||||
id = FileSys::StorageId::SdCard;
|
||||
} else {
|
||||
id = FileSys::StorageId::NandSystem;
|
||||
}
|
||||
|
||||
IFileSystem filesystem(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -812,7 +862,7 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space));
|
||||
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc));
|
||||
}
|
||||
|
||||
void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
@@ -836,7 +886,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
auto romfs = OpenRomFSCurrentProcess();
|
||||
auto romfs = fsc.OpenRomFSCurrentProcess();
|
||||
if (romfs.Failed()) {
|
||||
// TODO (bunnei): Find the right error code to use here
|
||||
LOG_CRITICAL(Service_FS, "no file system interface available!");
|
||||
@@ -861,7 +911,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
|
||||
static_cast<u8>(storage_id), unknown, title_id);
|
||||
|
||||
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||
auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||
|
||||
if (data.Failed()) {
|
||||
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
|
||||
|
||||
@@ -32,7 +32,7 @@ enum class LogMode : u32 {
|
||||
|
||||
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
|
||||
public:
|
||||
explicit FSP_SRV(const Core::Reporter& reporter);
|
||||
explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter);
|
||||
~FSP_SRV() override;
|
||||
|
||||
private:
|
||||
@@ -51,6 +51,8 @@ private:
|
||||
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
|
||||
void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSystemController& fsc;
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
u64 current_process_id = 0;
|
||||
u32 access_log_program_index = 0;
|
||||
|
||||
@@ -617,7 +617,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSystemController& fsc) {
|
||||
std::make_shared<NS>("ns:am2")->InstallAsService(service_manager);
|
||||
std::make_shared<NS>("ns:ec")->InstallAsService(service_manager);
|
||||
std::make_shared<NS>("ns:rid")->InstallAsService(service_manager);
|
||||
@@ -628,7 +628,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<NS_SU>()->InstallAsService(service_manager);
|
||||
std::make_shared<NS_VM>()->InstallAsService(service_manager);
|
||||
|
||||
std::make_shared<PL_U>()->InstallAsService(service_manager);
|
||||
std::make_shared<PL_U>(fsc)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Service::NS
|
||||
|
||||
@@ -6,7 +6,13 @@
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NS {
|
||||
namespace Service {
|
||||
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
} // namespace FileSystem
|
||||
|
||||
namespace NS {
|
||||
|
||||
class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
|
||||
public:
|
||||
@@ -91,6 +97,8 @@ private:
|
||||
};
|
||||
|
||||
/// Registers all NS services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, FileSystem::FileSystemController& fsc);
|
||||
|
||||
} // namespace Service::NS
|
||||
} // namespace NS
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -150,7 +150,8 @@ struct PL_U::Impl {
|
||||
std::vector<FontRegion> shared_font_regions;
|
||||
};
|
||||
|
||||
PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
|
||||
PL_U::PL_U(FileSystem::FileSystemController& fsc)
|
||||
: ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &PL_U::RequestLoad, "RequestLoad"},
|
||||
{1, &PL_U::GetLoadState, "GetLoadState"},
|
||||
@@ -161,7 +162,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
// Attempt to load shared font data from disk
|
||||
const auto* nand = FileSystem::GetSystemNANDContents();
|
||||
const auto* nand = fsc.GetSystemNANDContents();
|
||||
std::size_t offset = 0;
|
||||
// Rebuild shared fonts from data ncas
|
||||
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
|
||||
|
||||
@@ -7,11 +7,17 @@
|
||||
#include <memory>
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NS {
|
||||
namespace Service {
|
||||
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
} // namespace FileSystem
|
||||
|
||||
namespace NS {
|
||||
|
||||
class PL_U final : public ServiceFramework<PL_U> {
|
||||
public:
|
||||
PL_U();
|
||||
PL_U(FileSystem::FileSystemController& fsc);
|
||||
~PL_U() override;
|
||||
|
||||
private:
|
||||
@@ -26,4 +32,6 @@ private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace Service::NS
|
||||
} // namespace NS
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -146,8 +146,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
|
||||
}
|
||||
IoctlSubmitGpfifo params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
|
||||
params.address, params.num_entries, params.flags.raw);
|
||||
LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
|
||||
params.num_entries, params.flags.raw);
|
||||
|
||||
ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) +
|
||||
params.num_entries * sizeof(Tegra::CommandListHeader),
|
||||
@@ -179,8 +179,8 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
|
||||
}
|
||||
IoctlSubmitGpfifo params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}",
|
||||
params.address, params.num_entries, params.flags.raw);
|
||||
LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
|
||||
params.num_entries, params.flags.raw);
|
||||
|
||||
Tegra::CommandList entries(params.num_entries);
|
||||
Memory::ReadBlock(params.address, entries.data(),
|
||||
|
||||
@@ -2,34 +2,31 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <json.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/prepo/prepo.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::PlayReport {
|
||||
|
||||
class PlayReport final : public ServiceFramework<PlayReport> {
|
||||
public:
|
||||
explicit PlayReport(const char* name) : ServiceFramework{name} {
|
||||
explicit PlayReport(Core::System& system, const char* name)
|
||||
: ServiceFramework{name}, system(system) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{10100, nullptr, "SaveReportOld"},
|
||||
{10101, &PlayReport::SaveReportWithUserOld, "SaveReportWithUserOld"},
|
||||
{10102, nullptr, "SaveReport"},
|
||||
{10103, nullptr, "SaveReportWithUser"},
|
||||
{10100, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old>, "SaveReportOld"},
|
||||
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
|
||||
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10200, nullptr, "RequestImmediateTransmission"},
|
||||
{10300, nullptr, "GetTransmissionStatus"},
|
||||
{20100, nullptr, "SaveSystemReport"},
|
||||
{20101, nullptr, "SaveSystemReportWithUser"},
|
||||
{20100, &PlayReport::SaveSystemReport, "SaveSystemReport"},
|
||||
{20101, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"},
|
||||
{20200, nullptr, "SetOperationMode"},
|
||||
{30100, nullptr, "ClearStorage"},
|
||||
{30200, nullptr, "ClearStatistics"},
|
||||
@@ -47,7 +44,28 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) {
|
||||
template <Core::Reporter::PlayReportType Type>
|
||||
void SaveReport(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto process_id = rp.PopRaw<u64>();
|
||||
|
||||
const auto data1 = ctx.ReadBuffer(0);
|
||||
const auto data2 = ctx.ReadBuffer(1);
|
||||
|
||||
LOG_DEBUG(Service_PREPO,
|
||||
"called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}",
|
||||
static_cast<u8>(Type), process_id, data1.size(), data2.size());
|
||||
|
||||
const auto& reporter{system.GetReporter()};
|
||||
reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2},
|
||||
process_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
template <Core::Reporter::PlayReportType Type>
|
||||
void SaveReportWithUser(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto user_id = rp.PopRaw<u128>();
|
||||
const auto process_id = rp.PopRaw<u64>();
|
||||
@@ -57,24 +75,65 @@ private:
|
||||
|
||||
LOG_DEBUG(
|
||||
Service_PREPO,
|
||||
"called, user_id={:016X}{:016X}, unk1={:016X}, data1_size={:016X}, data2_size={:016X}",
|
||||
user_id[1], user_id[0], process_id, data1.size(), data2.size());
|
||||
"called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}, "
|
||||
"data2_size={:016X}",
|
||||
static_cast<u8>(Type), user_id[1], user_id[0], process_id, data1.size(), data2.size());
|
||||
|
||||
const auto& reporter{Core::System::GetInstance().GetReporter()};
|
||||
reporter.SavePlayReport(Core::CurrentProcess()->GetTitleID(), process_id, {data1, data2},
|
||||
user_id);
|
||||
const auto& reporter{system.GetReporter()};
|
||||
reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2},
|
||||
process_id, user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SaveSystemReport(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
const auto data1 = ctx.ReadBuffer(0);
|
||||
const auto data2 = ctx.ReadBuffer(1);
|
||||
|
||||
LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}",
|
||||
title_id, data1.size(), data2.size());
|
||||
|
||||
const auto& reporter{system.GetReporter()};
|
||||
reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2});
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SaveSystemReportWithUser(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto user_id = rp.PopRaw<u128>();
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
const auto data1 = ctx.ReadBuffer(0);
|
||||
const auto data2 = ctx.ReadBuffer(1);
|
||||
|
||||
LOG_DEBUG(Service_PREPO,
|
||||
"called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, "
|
||||
"data2_size={:016X}",
|
||||
user_id[1], user_id[0], title_id, data1.size(), data2.size());
|
||||
|
||||
const auto& reporter{system.GetReporter()};
|
||||
reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2},
|
||||
std::nullopt, user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager);
|
||||
std::make_shared<PlayReport>("prepo:a2")->InstallAsService(service_manager);
|
||||
std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager);
|
||||
std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager);
|
||||
std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager);
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
std::make_shared<PlayReport>(system, "prepo:a")->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<PlayReport>(system, "prepo:a2")->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<PlayReport>(system, "prepo:m")->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<PlayReport>(system, "prepo:s")->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<PlayReport>(system, "prepo:u")->InstallAsService(system.ServiceManager());
|
||||
}
|
||||
|
||||
} // namespace Service::PlayReport
|
||||
|
||||
@@ -10,6 +10,6 @@ class ServiceManager;
|
||||
|
||||
namespace Service::PlayReport {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
void InstallInterfaces(Core::System& system);
|
||||
|
||||
} // namespace Service::PlayReport
|
||||
|
||||
@@ -199,6 +199,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
|
||||
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
|
||||
// here and pass it into the respective InstallInterfaces functions.
|
||||
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system.CoreTiming());
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
|
||||
|
||||
SM::ServiceManager::InstallInterfaces(sm);
|
||||
|
||||
@@ -235,12 +236,12 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
|
||||
NIFM::InstallInterfaces(*sm);
|
||||
NIM::InstallInterfaces(*sm);
|
||||
NPNS::InstallInterfaces(*sm);
|
||||
NS::InstallInterfaces(*sm);
|
||||
NS::InstallInterfaces(*sm, system.GetFileSystemController());
|
||||
Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
|
||||
PCIe::InstallInterfaces(*sm);
|
||||
PCTL::InstallInterfaces(*sm);
|
||||
PCV::InstallInterfaces(*sm);
|
||||
PlayReport::InstallInterfaces(*sm);
|
||||
PlayReport::InstallInterfaces(system);
|
||||
PM::InstallInterfaces(system);
|
||||
PSC::InstallInterfaces(*sm);
|
||||
PSM::InstallInterfaces(*sm);
|
||||
|
||||
@@ -18,10 +18,6 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
class VfsFilesystem;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class ClientPort;
|
||||
class ServerPort;
|
||||
@@ -31,6 +27,10 @@ class HLERequestContext;
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace FileSystem {
|
||||
class FileSystemController;
|
||||
} // namespace FileSystem
|
||||
|
||||
namespace SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -176,7 +177,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
// Register the RomFS if a ".romfs" file was found
|
||||
if (romfs_iter != files.end() && *romfs_iter != nullptr) {
|
||||
romfs = *romfs_iter;
|
||||
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
|
||||
std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -57,7 +58,8 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
|
||||
}
|
||||
|
||||
if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
|
||||
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
|
||||
std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
@@ -214,7 +215,8 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
|
||||
}
|
||||
|
||||
if (romfs != nullptr) {
|
||||
Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
|
||||
std::make_unique<FileSys::RomFSFactory>(*this));
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
@@ -152,8 +152,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
auto& system = Core::System::GetInstance();
|
||||
const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
|
||||
if (!cheats.empty()) {
|
||||
system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base,
|
||||
load_base + program_image.size());
|
||||
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
@@ -26,20 +27,18 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
|
||||
|
||||
if (nsp->GetStatus() != ResultStatus::Success)
|
||||
return;
|
||||
if (nsp->IsExtractedType())
|
||||
return;
|
||||
|
||||
const auto control_nca =
|
||||
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
|
||||
if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
|
||||
return;
|
||||
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
|
||||
|
||||
if (nsp->IsExtractedType()) {
|
||||
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
|
||||
} else {
|
||||
const auto control_nca =
|
||||
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
|
||||
if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
|
||||
return;
|
||||
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
|
||||
|
||||
if (title_id == 0)
|
||||
return;
|
||||
|
||||
@@ -56,11 +55,11 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
if (nsp.GetStatus() == ResultStatus::Success) {
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) {
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Non-Ectracted Type case
|
||||
// Non-Extracted Type case
|
||||
if (!nsp.IsExtractedType() &&
|
||||
nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(nsp.GetNCAFile(
|
||||
@@ -77,7 +76,7 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
|
||||
return {ResultStatus::ErrorAlreadyLoaded, {}};
|
||||
}
|
||||
|
||||
if (title_id == 0) {
|
||||
if (!nsp->IsExtractedType() && title_id == 0) {
|
||||
return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
|
||||
}
|
||||
|
||||
@@ -91,7 +90,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
|
||||
return {nsp_program_status, {}};
|
||||
}
|
||||
|
||||
if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
|
||||
if (!nsp->IsExtractedType() &&
|
||||
nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
|
||||
if (!Core::Crypto::KeyManager::KeyFileExists(false)) {
|
||||
return {ResultStatus::ErrorMissingProductionKeyFile, {}};
|
||||
}
|
||||
@@ -106,7 +106,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
|
||||
|
||||
FileSys::VirtualFile update_raw;
|
||||
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
|
||||
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
|
||||
Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
|
||||
std::move(update_raw));
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
@@ -72,7 +73,8 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
|
||||
|
||||
FileSys::VirtualFile update_raw;
|
||||
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
|
||||
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
|
||||
Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
|
||||
std::move(update_raw));
|
||||
}
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
+7
-2
@@ -43,8 +43,13 @@ static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* me
|
||||
|
||||
// During boot, current_page_table might not be set yet, in which case we need not flush
|
||||
if (Core::System::GetInstance().IsPoweredOn()) {
|
||||
Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS,
|
||||
size * PAGE_SIZE);
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
for (u64 i = 0; i < size; i++) {
|
||||
const auto page = base + i;
|
||||
if (page_table.attributes[page] == Common::PageType::RasterizerCachedMemory) {
|
||||
gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VAddr end = base + size;
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12);
|
||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
|
||||
|
||||
StandardVmCallbacks::StandardVmCallbacks(const Core::System& system,
|
||||
const CheatProcessMetadata& metadata)
|
||||
: system(system), metadata(metadata) {}
|
||||
|
||||
StandardVmCallbacks::~StandardVmCallbacks() = default;
|
||||
|
||||
void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
|
||||
ReadBlock(SanitizeAddress(address), data, size);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
|
||||
WriteBlock(SanitizeAddress(address), data, size);
|
||||
}
|
||||
|
||||
u64 StandardVmCallbacks::HidKeysDown() {
|
||||
const auto applet_resource =
|
||||
system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
|
||||
if (applet_resource == nullptr) {
|
||||
LOG_WARNING(CheatEngine,
|
||||
"Attempted to read input state, but applet resource is not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto press_state =
|
||||
applet_resource
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
|
||||
.GetAndResetPressState();
|
||||
return press_state & KEYPAD_BITMASK;
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::CommandLog(std::string_view data) {
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
|
||||
if ((in < metadata.main_nso_extents.base ||
|
||||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
|
||||
(in < metadata.heap_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
|
||||
LOG_ERROR(CheatEngine,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this "
|
||||
"persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
namespace {
|
||||
template <char match>
|
||||
std::string_view ExtractName(std::string_view data, std::size_t start_index) {
|
||||
auto end_index = start_index;
|
||||
while (data[end_index] != match) {
|
||||
++end_index;
|
||||
if (end_index > data.size() ||
|
||||
(end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return data.substr(start_index, end_index - start_index);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
|
||||
std::string_view data) const {
|
||||
std::vector<CheatEntry> out(1);
|
||||
std::optional<u64> current_entry = std::nullopt;
|
||||
|
||||
for (std::size_t i = 0; i < data.size(); ++i) {
|
||||
if (::isspace(data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[i] == '{') {
|
||||
current_entry = 0;
|
||||
|
||||
if (out[*current_entry].definition.num_opcodes > 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto name = ExtractName<'}'>(data, i + 1);
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name.length() + 1;
|
||||
} else if (data[i] == '[') {
|
||||
current_entry = out.size();
|
||||
out.emplace_back();
|
||||
|
||||
const auto name = ExtractName<']'>(data, i + 1);
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name.length() + 1;
|
||||
} else if (::isxdigit(data[i])) {
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
||||
out[*current_entry].definition.opcodes.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto hex = std::string(data.substr(i, 8));
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
||||
std::stoul(hex, nullptr, 0x10);
|
||||
|
||||
i += 8;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0;
|
||||
out[0].cheat_id = 0;
|
||||
|
||||
for (u32 i = 1; i < out.size(); ++i) {
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0;
|
||||
out[i].cheat_id = i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats,
|
||||
const std::array<u8, 0x20>& build_id)
|
||||
: system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>(
|
||||
system, metadata)},
|
||||
cheats(std::move(cheats)) {
|
||||
metadata.main_nso_build_id = build_id;
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void CheatEngine::Initialize() {
|
||||
event = core_timing.RegisterEvent(
|
||||
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
|
||||
|
||||
metadata.process_id = system.CurrentProcess()->GetProcessID();
|
||||
metadata.title_id = system.CurrentProcess()->GetTitleID();
|
||||
|
||||
const auto& vm_manager = system.CurrentProcess()->VMManager();
|
||||
metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
|
||||
metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
|
||||
vm_manager.GetAddressSpaceSize()};
|
||||
metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
|
||||
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
|
||||
metadata.main_nso_extents = {main_region_begin, main_region_size};
|
||||
}
|
||||
|
||||
void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
|
||||
this->cheats = std::move(cheats);
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
||||
|
||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
|
||||
if (is_pending_reload.exchange(false)) {
|
||||
vm.LoadProgram(cheats);
|
||||
}
|
||||
|
||||
if (vm.GetProgramSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
vm.Execute(metadata);
|
||||
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace Memory
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
#include "core/memory/dmnt_cheat_vm.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Memory {
|
||||
|
||||
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
|
||||
public:
|
||||
StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata);
|
||||
~StandardVmCallbacks() override;
|
||||
|
||||
void MemoryRead(VAddr address, void* data, u64 size) override;
|
||||
void MemoryWrite(VAddr address, const void* data, u64 size) override;
|
||||
u64 HidKeysDown() override;
|
||||
void DebugLog(u8 id, u64 value) override;
|
||||
void CommandLog(std::string_view data) override;
|
||||
|
||||
private:
|
||||
VAddr SanitizeAddress(VAddr address) const;
|
||||
|
||||
const CheatProcessMetadata& metadata;
|
||||
const Core::System& system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
virtual std::vector<CheatEntry> Parse(const Core::System& system,
|
||||
std::string_view data) const = 0;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_,
|
||||
const std::array<u8, 0x20>& build_id);
|
||||
~CheatEngine();
|
||||
|
||||
void Initialize();
|
||||
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
|
||||
|
||||
void Reload(std::vector<CheatEntry> cheats);
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, s64 cycles_late);
|
||||
|
||||
DmntCheatVm vm;
|
||||
CheatProcessMetadata metadata;
|
||||
|
||||
std::vector<CheatEntry> cheats;
|
||||
std::atomic_bool is_pending_reload{false};
|
||||
|
||||
Core::Timing::EventType* event{};
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Memory
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted by DarkLordZach for use/interaction with yuzu
|
||||
*
|
||||
* Modifications Copyright 2019 yuzu emulator team
|
||||
* Licensed under GPLv2 or any later version
|
||||
* Refer to the license.txt file included.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
struct MemoryRegionExtents {
|
||||
u64 base{};
|
||||
u64 size{};
|
||||
};
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id{};
|
||||
u64 title_id{};
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents address_space_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
|
||||
struct CheatDefinition {
|
||||
std::array<char, 0x40> readable_name{};
|
||||
u32 num_opcodes{};
|
||||
std::array<u32, 0x100> opcodes{};
|
||||
};
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled{};
|
||||
u32 cheat_id{};
|
||||
CheatDefinition definition{};
|
||||
};
|
||||
|
||||
} // namespace Memory
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted by DarkLordZach for use/interaction with yuzu
|
||||
*
|
||||
* Modifications Copyright 2019 yuzu emulator team
|
||||
* Licensed under GPLv2 or any later version
|
||||
* Refer to the license.txt file included.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <fmt/printf.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
enum class CheatVmOpcodeType : u32 {
|
||||
StoreStatic = 0,
|
||||
BeginConditionalBlock = 1,
|
||||
EndConditionalBlock = 2,
|
||||
ControlLoop = 3,
|
||||
LoadRegisterStatic = 4,
|
||||
LoadRegisterMemory = 5,
|
||||
StoreStaticToAddress = 6,
|
||||
PerformArithmeticStatic = 7,
|
||||
BeginKeypressConditionalBlock = 8,
|
||||
|
||||
// These are not implemented by Gateway's VM.
|
||||
PerformArithmeticRegister = 9,
|
||||
StoreRegisterToAddress = 10,
|
||||
Reserved11 = 11,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
ExtendedWidth = 12,
|
||||
|
||||
// Extended width opcodes.
|
||||
BeginRegisterConditionalBlock = 0xC0,
|
||||
SaveRestoreRegister = 0xC1,
|
||||
SaveRestoreRegisterMask = 0xC2,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
GT = 1,
|
||||
GE = 2,
|
||||
LT = 3,
|
||||
LE = 4,
|
||||
EQ = 5,
|
||||
NE = 6,
|
||||
};
|
||||
|
||||
enum class RegisterArithmeticType : u32 {
|
||||
Addition = 0,
|
||||
Subtraction = 1,
|
||||
Multiplication = 2,
|
||||
LeftShift = 3,
|
||||
RightShift = 4,
|
||||
|
||||
// These are not supported by Gateway's VM.
|
||||
LogicalAnd = 5,
|
||||
LogicalOr = 6,
|
||||
LogicalNot = 7,
|
||||
LogicalXor = 8,
|
||||
|
||||
None = 9,
|
||||
};
|
||||
|
||||
enum class StoreRegisterOffsetType : u32 {
|
||||
None = 0,
|
||||
Reg = 1,
|
||||
Imm = 2,
|
||||
MemReg = 3,
|
||||
MemImm = 4,
|
||||
MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum class CompareRegisterValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
StaticValue = 4,
|
||||
OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum class SaveRestoreRegisterOpType : u32 {
|
||||
Restore = 0,
|
||||
Save = 1,
|
||||
ClearSaved = 2,
|
||||
ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum class DebugLogValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 offset_register{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
u32 reg_index{};
|
||||
u32 num_iters{};
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 reg_index{};
|
||||
bool load_from_reg{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
bool increment_reg{};
|
||||
bool add_offset_reg{};
|
||||
u32 offset_reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 value{};
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 dst_reg_index{};
|
||||
u32 src_reg_1_index{};
|
||||
u32 src_reg_2_index{};
|
||||
bool has_immediate{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 str_reg_index{};
|
||||
u32 addr_reg_index{};
|
||||
bool increment_reg{};
|
||||
StoreRegisterOffsetType ofs_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u32 val_reg_index{};
|
||||
CompareRegisterValueType comp_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 other_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index{};
|
||||
u32 src_index{};
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
std::array<bool, 0x10> should_operate{};
|
||||
};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
DebugLogValueType val_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 val_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct UnrecognizedInstruction {
|
||||
CheatVmOpcodeType opcode{};
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
bool begin_conditional_block{};
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
class DmntCheatVm {
|
||||
public:
|
||||
/// Helper Type for DmntCheatVm <=> yuzu Interface
|
||||
class Callbacks {
|
||||
public:
|
||||
virtual ~Callbacks();
|
||||
|
||||
virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
|
||||
virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
|
||||
|
||||
virtual u64 HidKeysDown() = 0;
|
||||
|
||||
virtual void DebugLog(u8 id, u64 value) = 0;
|
||||
virtual void CommandLog(std::string_view data) = 0;
|
||||
};
|
||||
|
||||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
|
||||
static constexpr std::size_t NumRegisters = 0x10;
|
||||
|
||||
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks);
|
||||
~DmntCheatVm();
|
||||
|
||||
std::size_t GetProgramSize() const {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(const std::vector<CheatEntry>& cheats);
|
||||
void Execute(const CheatProcessMetadata& metadata);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Callbacks> callbacks;
|
||||
|
||||
std::size_t num_opcodes = 0;
|
||||
std::size_t instruction_ptr = 0;
|
||||
std::size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
||||
std::array<u64, NumRegisters> registers{};
|
||||
std::array<u64, NumRegisters> saved_values{};
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock();
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
void DebugLog(u32 log_id, u64 value);
|
||||
|
||||
void LogOpcode(const CheatVmOpcode& opcode);
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
MemoryAccessType mem_type, u64 rel_address);
|
||||
};
|
||||
|
||||
}; // namespace Memory
|
||||
+46
-1
@@ -4,8 +4,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
#include "common/file_util.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
@@ -15,8 +21,31 @@ using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::microseconds;
|
||||
|
||||
// Purposefully ignore the first five frames, as there's a significant amount of overhead in
|
||||
// booting that we shouldn't account for
|
||||
constexpr std::size_t IgnoreFrames = 5;
|
||||
|
||||
namespace Core {
|
||||
|
||||
PerfStats::PerfStats(u64 title_id) : title_id(title_id) {}
|
||||
|
||||
PerfStats::~PerfStats() {
|
||||
if (!Settings::values.record_frame_times || title_id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::time_t t = std::time(nullptr);
|
||||
std::ostringstream stream;
|
||||
std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
|
||||
std::ostream_iterator<double>(stream, "\n"));
|
||||
const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||
// %F Date format expanded is "%Y-%m-%d"
|
||||
const std::string filename =
|
||||
fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id);
|
||||
FileUtil::IOFile file(filename, "w");
|
||||
file.WriteString(stream.str());
|
||||
}
|
||||
|
||||
void PerfStats::BeginSystemFrame() {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
@@ -27,7 +56,12 @@ void PerfStats::EndSystemFrame() {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
auto frame_end = Clock::now();
|
||||
accumulated_frametime += frame_end - frame_begin;
|
||||
const auto frame_time = frame_end - frame_begin;
|
||||
if (current_index < perf_history.size()) {
|
||||
perf_history[current_index++] =
|
||||
std::chrono::duration<double, std::milli>(frame_time).count();
|
||||
}
|
||||
accumulated_frametime += frame_time;
|
||||
system_frames += 1;
|
||||
|
||||
previous_frame_length = frame_end - previous_frame_end;
|
||||
@@ -40,6 +74,17 @@ void PerfStats::EndGameFrame() {
|
||||
game_frames += 1;
|
||||
}
|
||||
|
||||
double PerfStats::GetMeanFrametime() {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
if (current_index <= IgnoreFrames) {
|
||||
return 0;
|
||||
}
|
||||
const double sum = std::accumulate(perf_history.begin() + IgnoreFrames,
|
||||
perf_history.begin() + current_index, 0);
|
||||
return sum / (current_index - IgnoreFrames);
|
||||
}
|
||||
|
||||
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
|
||||
std::lock_guard lock{object_mutex};
|
||||
|
||||
|
||||
+20
-1
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -27,6 +29,10 @@ struct PerfStatsResults {
|
||||
*/
|
||||
class PerfStats {
|
||||
public:
|
||||
explicit PerfStats(u64 title_id);
|
||||
|
||||
~PerfStats();
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
void BeginSystemFrame();
|
||||
@@ -35,6 +41,11 @@ public:
|
||||
|
||||
PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);
|
||||
|
||||
/**
|
||||
* Returns the Arthimetic Mean of all frametime values stored in the performance history.
|
||||
*/
|
||||
double GetMeanFrametime();
|
||||
|
||||
/**
|
||||
* Gets the ratio between walltime and the emulated time of the previous system frame. This is
|
||||
* useful for scaling inputs or outputs moving between the two time domains.
|
||||
@@ -42,7 +53,15 @@ public:
|
||||
double GetLastFrameTimeScale();
|
||||
|
||||
private:
|
||||
std::mutex object_mutex;
|
||||
std::mutex object_mutex{};
|
||||
|
||||
/// Title ID for the game that is running. 0 if there is no game running yet
|
||||
u64 title_id{0};
|
||||
/// Current index for writing to the perf_history array
|
||||
std::size_t current_index{0};
|
||||
/// Stores an hour of historical frametime data useful for processing and tracking performance
|
||||
/// regressions with code changes.
|
||||
std::array<double, 216000> perf_history = {};
|
||||
|
||||
/// Point when the cumulative counters were reset
|
||||
Clock::time_point reset_point = Clock::now();
|
||||
|
||||
@@ -304,8 +304,8 @@ void Reporter::SaveUnimplementedAppletReport(
|
||||
SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
|
||||
}
|
||||
|
||||
void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
|
||||
std::optional<u128> user_id) const {
|
||||
void Reporter::SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data,
|
||||
std::optional<u64> process_id, std::optional<u128> user_id) const {
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -321,7 +321,11 @@ void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vec
|
||||
data_out.push_back(Common::HexToString(d));
|
||||
}
|
||||
|
||||
out["play_report_process_id"] = fmt::format("{:016X}", process_id);
|
||||
if (process_id.has_value()) {
|
||||
out["play_report_process_id"] = fmt::format("{:016X}", *process_id);
|
||||
}
|
||||
|
||||
out["play_report_type"] = fmt::format("{:02}", static_cast<u8>(type));
|
||||
out["play_report_data"] = std::move(data_out);
|
||||
|
||||
SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
|
||||
|
||||
+8
-2
@@ -46,8 +46,14 @@ public:
|
||||
std::vector<std::vector<u8>> normal_channel,
|
||||
std::vector<std::vector<u8>> interactive_channel) const;
|
||||
|
||||
void SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
|
||||
std::optional<u128> user_id = {}) const;
|
||||
enum class PlayReportType {
|
||||
Old,
|
||||
New,
|
||||
System,
|
||||
};
|
||||
|
||||
void SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data,
|
||||
std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const;
|
||||
|
||||
void SaveErrorReport(u64 title_id, ResultCode result,
|
||||
std::optional<std::string> custom_text_main = {},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
@@ -97,8 +98,8 @@ void LogSettings() {
|
||||
LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
|
||||
LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
|
||||
LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd);
|
||||
LogSetting("DataStorage_NandDir", Settings::values.nand_dir);
|
||||
LogSetting("DataStorage_SdmcDir", Settings::values.sdmc_dir);
|
||||
LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
|
||||
LogSetting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
|
||||
LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub);
|
||||
LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port);
|
||||
LogSetting("Debugging_ProgramArgs", Settings::values.program_args);
|
||||
|
||||
+33
-2
@@ -346,6 +346,31 @@ struct TouchscreenInput {
|
||||
u32 rotation_angle;
|
||||
};
|
||||
|
||||
enum class NANDTotalSize : u64 {
|
||||
S29_1GB = 0x747C00000ULL,
|
||||
};
|
||||
|
||||
enum class NANDUserSize : u64 {
|
||||
S26GB = 0x680000000ULL,
|
||||
};
|
||||
|
||||
enum class NANDSystemSize : u64 {
|
||||
S2_5GB = 0xA0000000,
|
||||
};
|
||||
|
||||
enum class SDMCSize : u64 {
|
||||
S1GB = 0x40000000,
|
||||
S2GB = 0x80000000,
|
||||
S4GB = 0x100000000ULL,
|
||||
S8GB = 0x200000000ULL,
|
||||
S16GB = 0x400000000ULL,
|
||||
S32GB = 0x800000000ULL,
|
||||
S64GB = 0x1000000000ULL,
|
||||
S128GB = 0x2000000000ULL,
|
||||
S256GB = 0x4000000000ULL,
|
||||
S1TB = 0x10000000000ULL,
|
||||
};
|
||||
|
||||
struct Values {
|
||||
// System
|
||||
bool use_docked_mode;
|
||||
@@ -382,8 +407,13 @@ struct Values {
|
||||
|
||||
// Data Storage
|
||||
bool use_virtual_sd;
|
||||
std::string nand_dir;
|
||||
std::string sdmc_dir;
|
||||
bool gamecard_inserted;
|
||||
bool gamecard_current_game;
|
||||
std::string gamecard_path;
|
||||
NANDTotalSize nand_total_size;
|
||||
NANDSystemSize nand_system_size;
|
||||
NANDUserSize nand_user_size;
|
||||
SDMCSize sdmc_size;
|
||||
|
||||
// Renderer
|
||||
float resolution_factor;
|
||||
@@ -409,6 +439,7 @@ struct Values {
|
||||
float volume;
|
||||
|
||||
// Debugging
|
||||
bool record_frame_times;
|
||||
bool use_gdbstub;
|
||||
u16 gdbstub_port;
|
||||
std::string program_args;
|
||||
|
||||
@@ -541,7 +541,7 @@ void Maxwell3D::ProcessSyncPoint() {
|
||||
}
|
||||
|
||||
void Maxwell3D::DrawArrays() {
|
||||
LOG_DEBUG(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
|
||||
LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
|
||||
regs.vertex_buffer.count);
|
||||
ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
|
||||
|
||||
|
||||
@@ -566,6 +566,13 @@ enum class ImageAtomicOperation : u64 {
|
||||
Exch = 8,
|
||||
};
|
||||
|
||||
enum class ShuffleOperation : u64 {
|
||||
Idx = 0, // shuffleNV
|
||||
Up = 1, // shuffleUpNV
|
||||
Down = 2, // shuffleDownNV
|
||||
Bfly = 3, // shuffleXorNV
|
||||
};
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
@@ -599,6 +606,15 @@ union Instruction {
|
||||
BitField<42, 1, u64> negate_value;
|
||||
} vote;
|
||||
|
||||
union {
|
||||
BitField<30, 2, ShuffleOperation> operation;
|
||||
BitField<48, 3, u64> pred48;
|
||||
BitField<28, 1, u64> is_index_imm;
|
||||
BitField<29, 1, u64> is_mask_imm;
|
||||
BitField<20, 5, u64> index_imm;
|
||||
BitField<34, 13, u64> mask_imm;
|
||||
} shfl;
|
||||
|
||||
union {
|
||||
BitField<8, 8, Register> gpr;
|
||||
BitField<20, 24, s64> offset;
|
||||
@@ -933,6 +949,11 @@ union Instruction {
|
||||
BitField<49, 3, PredCondition> cond;
|
||||
} isetp;
|
||||
|
||||
union {
|
||||
BitField<48, 1, u64> is_signed;
|
||||
BitField<49, 3, PredCondition> cond;
|
||||
} icmp;
|
||||
|
||||
union {
|
||||
BitField<0, 3, u64> pred0;
|
||||
BitField<3, 3, u64> pred3;
|
||||
@@ -1542,6 +1563,7 @@ public:
|
||||
BRK,
|
||||
DEPBAR,
|
||||
VOTE,
|
||||
SHFL,
|
||||
BFE_C,
|
||||
BFE_R,
|
||||
BFE_IMM,
|
||||
@@ -1628,6 +1650,10 @@ public:
|
||||
SEL_C,
|
||||
SEL_R,
|
||||
SEL_IMM,
|
||||
ICMP_RC,
|
||||
ICMP_R,
|
||||
ICMP_CR,
|
||||
ICMP_IMM,
|
||||
MUFU, // Multi-Function Operator
|
||||
RRO_C, // Range Reduction Operator
|
||||
RRO_R,
|
||||
@@ -1833,6 +1859,7 @@ private:
|
||||
INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"),
|
||||
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
|
||||
INST("0101000011011---", Id::VOTE, Type::Warp, "VOTE"),
|
||||
INST("1110111100010---", Id::SHFL, Type::Warp, "SHFL"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
|
||||
INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
|
||||
@@ -1892,6 +1919,10 @@ private:
|
||||
INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"),
|
||||
INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"),
|
||||
INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"),
|
||||
INST("010100110100----", Id::ICMP_RC, Type::ArithmeticInteger, "ICMP_RC"),
|
||||
INST("010110110100----", Id::ICMP_R, Type::ArithmeticInteger, "ICMP_R"),
|
||||
INST("010010110100----", Id::ICMP_CR, Type::ArithmeticInteger, "ICMP_CR"),
|
||||
INST("0011011-0100----", Id::ICMP_IMM, Type::ArithmeticInteger, "ICMP_IMM"),
|
||||
INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"),
|
||||
INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"),
|
||||
INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"),
|
||||
|
||||
@@ -214,7 +214,8 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
|
||||
std::string source = "#version 430 core\n"
|
||||
"#extension GL_ARB_separate_shader_objects : enable\n"
|
||||
"#extension GL_NV_gpu_shader5 : enable\n"
|
||||
"#extension GL_NV_shader_thread_group : enable\n";
|
||||
"#extension GL_NV_shader_thread_group : enable\n"
|
||||
"#extension GL_NV_shader_thread_shuffle : enable\n";
|
||||
if (entries.shader_viewport_layer_array) {
|
||||
source += "#extension GL_ARB_shader_viewport_layer_array : enable\n";
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ public:
|
||||
DeclareRegisters();
|
||||
DeclarePredicates();
|
||||
DeclareLocalMemory();
|
||||
DeclareSharedMemory();
|
||||
DeclareInternalFlags();
|
||||
DeclareInputAttributes();
|
||||
DeclareOutputAttributes();
|
||||
@@ -499,6 +500,13 @@ private:
|
||||
code.AddNewLine();
|
||||
}
|
||||
|
||||
void DeclareSharedMemory() {
|
||||
if (stage != ProgramType::Compute) {
|
||||
return;
|
||||
}
|
||||
code.AddLine("shared uint {}[];", GetSharedMemory());
|
||||
}
|
||||
|
||||
void DeclareInternalFlags() {
|
||||
for (u32 flag = 0; flag < static_cast<u32>(InternalFlag::Amount); flag++) {
|
||||
const auto flag_code = static_cast<InternalFlag>(flag);
|
||||
@@ -881,6 +889,12 @@ private:
|
||||
Type::Uint};
|
||||
}
|
||||
|
||||
if (const auto smem = std::get_if<SmemNode>(&*node)) {
|
||||
return {
|
||||
fmt::format("{}[{} >> 2]", GetSharedMemory(), Visit(smem->GetAddress()).AsUint()),
|
||||
Type::Uint};
|
||||
}
|
||||
|
||||
if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
|
||||
return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool};
|
||||
}
|
||||
@@ -1007,10 +1021,10 @@ private:
|
||||
return {std::move(temporary), value.GetType()};
|
||||
}
|
||||
|
||||
Expression GetOutputAttribute(const AbufNode* abuf) {
|
||||
std::optional<Expression> GetOutputAttribute(const AbufNode* abuf) {
|
||||
switch (const auto attribute = abuf->GetIndex()) {
|
||||
case Attribute::Index::Position:
|
||||
return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float};
|
||||
return {{"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}};
|
||||
case Attribute::Index::LayerViewportPointSize:
|
||||
switch (abuf->GetElement()) {
|
||||
case 0:
|
||||
@@ -1020,25 +1034,25 @@ private:
|
||||
if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
|
||||
return {};
|
||||
}
|
||||
return {"gl_Layer", Type::Int};
|
||||
return {{"gl_Layer", Type::Int}};
|
||||
case 2:
|
||||
if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
|
||||
return {};
|
||||
}
|
||||
return {"gl_ViewportIndex", Type::Int};
|
||||
return {{"gl_ViewportIndex", Type::Int}};
|
||||
case 3:
|
||||
UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
|
||||
return {"gl_PointSize", Type::Float};
|
||||
return {{"gl_PointSize", Type::Float}};
|
||||
}
|
||||
return {};
|
||||
case Attribute::Index::ClipDistances0123:
|
||||
return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float};
|
||||
return {{fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}};
|
||||
case Attribute::Index::ClipDistances4567:
|
||||
return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float};
|
||||
return {{fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}};
|
||||
default:
|
||||
if (IsGenericAttribute(attribute)) {
|
||||
return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()),
|
||||
Type::Float};
|
||||
return {
|
||||
{GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), Type::Float}};
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
|
||||
return {};
|
||||
@@ -1278,7 +1292,11 @@ private:
|
||||
target = {GetRegister(gpr->GetIndex()), Type::Float};
|
||||
} else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
|
||||
UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
|
||||
target = GetOutputAttribute(abuf);
|
||||
auto output = GetOutputAttribute(abuf);
|
||||
if (!output) {
|
||||
return {};
|
||||
}
|
||||
target = std::move(*output);
|
||||
} else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
|
||||
if (stage == ProgramType::Compute) {
|
||||
LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
|
||||
@@ -1286,6 +1304,11 @@ private:
|
||||
target = {
|
||||
fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
|
||||
Type::Uint};
|
||||
} else if (const auto smem = std::get_if<SmemNode>(&*dest)) {
|
||||
ASSERT(stage == ProgramType::Compute);
|
||||
target = {
|
||||
fmt::format("{}[{} >> 2]", GetSharedMemory(), Visit(smem->GetAddress()).AsUint()),
|
||||
Type::Uint};
|
||||
} else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
|
||||
const std::string real = Visit(gmem->GetRealAddress()).AsUint();
|
||||
const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
|
||||
@@ -1934,8 +1957,7 @@ private:
|
||||
Expression BallotThread(Operation operation) {
|
||||
const std::string value = VisitOperand(operation, 0).AsBool();
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Nvidia warp intrinsics are not available and its required by a shader");
|
||||
LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader");
|
||||
// Stub on non-Nvidia devices by simulating all threads voting the same as the active
|
||||
// one.
|
||||
return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint};
|
||||
@@ -1946,8 +1968,7 @@ private:
|
||||
Expression Vote(Operation operation, const char* func) {
|
||||
const std::string value = VisitOperand(operation, 0).AsBool();
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Nvidia vote intrinsics are not available and its required by a shader");
|
||||
LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader");
|
||||
// Stub with a warp size of one.
|
||||
return {value, Type::Bool};
|
||||
}
|
||||
@@ -1964,15 +1985,54 @@ private:
|
||||
|
||||
Expression VoteEqual(Operation operation) {
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Nvidia vote intrinsics are not available and its required by a shader");
|
||||
// We must return true here since a stub for a theoretical warp size of 1 will always
|
||||
// return an equal result for all its votes.
|
||||
LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader");
|
||||
// We must return true here since a stub for a theoretical warp size of 1.
|
||||
// This will always return an equal result across all votes.
|
||||
return {"true", Type::Bool};
|
||||
}
|
||||
return Vote(operation, "allThreadsEqualNV");
|
||||
}
|
||||
|
||||
template <const std::string_view& func>
|
||||
Expression Shuffle(Operation operation) {
|
||||
const std::string value = VisitOperand(operation, 0).AsFloat();
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
LOG_ERROR(Render_OpenGL, "Nvidia shuffle intrinsics are required by this shader");
|
||||
// On a "single-thread" device we are either on the same thread or out of bounds. Both
|
||||
// cases return the passed value.
|
||||
return {value, Type::Float};
|
||||
}
|
||||
|
||||
const std::string index = VisitOperand(operation, 1).AsUint();
|
||||
const std::string width = VisitOperand(operation, 2).AsUint();
|
||||
return {fmt::format("{}({}, {}, {})", func, value, index, width), Type::Float};
|
||||
}
|
||||
|
||||
template <const std::string_view& func>
|
||||
Expression InRangeShuffle(Operation operation) {
|
||||
const std::string index = VisitOperand(operation, 0).AsUint();
|
||||
const std::string width = VisitOperand(operation, 1).AsUint();
|
||||
if (!device.HasWarpIntrinsics()) {
|
||||
// On a "single-thread" device we are only in bounds when the requested index is 0.
|
||||
return {fmt::format("({} == 0U)", index), Type::Bool};
|
||||
}
|
||||
|
||||
const std::string in_range = code.GenerateTemporary();
|
||||
code.AddLine("bool {};", in_range);
|
||||
code.AddLine("{}(0U, {}, {}, {});", func, index, width, in_range);
|
||||
return {in_range, Type::Bool};
|
||||
}
|
||||
|
||||
struct Func final {
|
||||
Func() = delete;
|
||||
~Func() = delete;
|
||||
|
||||
static constexpr std::string_view ShuffleIndexed = "shuffleNV";
|
||||
static constexpr std::string_view ShuffleUp = "shuffleUpNV";
|
||||
static constexpr std::string_view ShuffleDown = "shuffleDownNV";
|
||||
static constexpr std::string_view ShuffleButterfly = "shuffleXorNV";
|
||||
};
|
||||
|
||||
static constexpr std::array operation_decompilers = {
|
||||
&GLSLDecompiler::Assign,
|
||||
|
||||
@@ -2135,6 +2195,16 @@ private:
|
||||
&GLSLDecompiler::VoteAll,
|
||||
&GLSLDecompiler::VoteAny,
|
||||
&GLSLDecompiler::VoteEqual,
|
||||
|
||||
&GLSLDecompiler::Shuffle<Func::ShuffleIndexed>,
|
||||
&GLSLDecompiler::Shuffle<Func::ShuffleUp>,
|
||||
&GLSLDecompiler::Shuffle<Func::ShuffleDown>,
|
||||
&GLSLDecompiler::Shuffle<Func::ShuffleButterfly>,
|
||||
|
||||
&GLSLDecompiler::InRangeShuffle<Func::ShuffleIndexed>,
|
||||
&GLSLDecompiler::InRangeShuffle<Func::ShuffleUp>,
|
||||
&GLSLDecompiler::InRangeShuffle<Func::ShuffleDown>,
|
||||
&GLSLDecompiler::InRangeShuffle<Func::ShuffleButterfly>,
|
||||
};
|
||||
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
|
||||
|
||||
@@ -2175,6 +2245,10 @@ private:
|
||||
return "lmem_" + suffix;
|
||||
}
|
||||
|
||||
std::string GetSharedMemory() const {
|
||||
return fmt::format("smem_{}", suffix);
|
||||
}
|
||||
|
||||
std::string GetInternalFlag(InternalFlag flag) const {
|
||||
constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag",
|
||||
"overflow_flag"};
|
||||
|
||||
@@ -145,7 +145,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
|
||||
case Tegra::Texture::TextureMipmapFilter::None:
|
||||
return GL_LINEAR;
|
||||
case Tegra::Texture::TextureMipmapFilter::Nearest:
|
||||
return GL_NEAREST_MIPMAP_LINEAR;
|
||||
return GL_LINEAR_MIPMAP_NEAREST;
|
||||
case Tegra::Texture::TextureMipmapFilter::Linear:
|
||||
return GL_LINEAR_MIPMAP_LINEAR;
|
||||
}
|
||||
@@ -157,7 +157,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
|
||||
case Tegra::Texture::TextureMipmapFilter::Nearest:
|
||||
return GL_NEAREST_MIPMAP_NEAREST;
|
||||
case Tegra::Texture::TextureMipmapFilter::Linear:
|
||||
return GL_LINEAR_MIPMAP_NEAREST;
|
||||
return GL_NEAREST_MIPMAP_LINEAR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,6 +1127,46 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
Id ShuffleIndexed(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id ShuffleUp(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id ShuffleDown(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id ShuffleButterfly(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id InRangeShuffleIndexed(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id InRangeShuffleUp(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id InRangeShuffleDown(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id InRangeShuffleButterfly(Operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type,
|
||||
const std::string& name) {
|
||||
const Id id = OpVariable(type, storage);
|
||||
@@ -1431,6 +1471,16 @@ private:
|
||||
&SPIRVDecompiler::VoteAll,
|
||||
&SPIRVDecompiler::VoteAny,
|
||||
&SPIRVDecompiler::VoteEqual,
|
||||
|
||||
&SPIRVDecompiler::ShuffleIndexed,
|
||||
&SPIRVDecompiler::ShuffleUp,
|
||||
&SPIRVDecompiler::ShuffleDown,
|
||||
&SPIRVDecompiler::ShuffleButterfly,
|
||||
|
||||
&SPIRVDecompiler::InRangeShuffleIndexed,
|
||||
&SPIRVDecompiler::InRangeShuffleUp,
|
||||
&SPIRVDecompiler::InRangeShuffleDown,
|
||||
&SPIRVDecompiler::InRangeShuffleButterfly,
|
||||
};
|
||||
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
|
||||
|
||||
|
||||
@@ -138,6 +138,35 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
|
||||
SetRegister(bb, instr.gpr0, value);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::ICMP_CR:
|
||||
case OpCode::Id::ICMP_R:
|
||||
case OpCode::Id::ICMP_RC:
|
||||
case OpCode::Id::ICMP_IMM: {
|
||||
const Node zero = Immediate(0);
|
||||
|
||||
const auto [op_b, test] = [&]() -> std::pair<Node, Node> {
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::ICMP_CR:
|
||||
return {GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset),
|
||||
GetRegister(instr.gpr39)};
|
||||
case OpCode::Id::ICMP_R:
|
||||
return {GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
|
||||
case OpCode::Id::ICMP_RC:
|
||||
return {GetRegister(instr.gpr39),
|
||||
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset)};
|
||||
case OpCode::Id::ICMP_IMM:
|
||||
return {Immediate(instr.alu.GetSignedImm20_20()), GetRegister(instr.gpr39)};
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return {zero, zero};
|
||||
}
|
||||
}();
|
||||
const Node op_a = GetRegister(instr.gpr8);
|
||||
const Node comparison =
|
||||
GetPredicateComparisonInteger(instr.icmp.cond, instr.icmp.is_signed != 0, test, zero);
|
||||
SetRegister(bb, instr.gpr0, Operation(OperationCode::Select, comparison, op_a, op_b));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::LOP_C:
|
||||
case OpCode::Id::LOP_R:
|
||||
case OpCode::Id::LOP_IMM: {
|
||||
|
||||
@@ -35,7 +35,7 @@ u32 GetUniformTypeElementsCount(Tegra::Shader::UniformType uniform_type) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
} // Anonymous namespace
|
||||
|
||||
u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
const Instruction instr = {program_code[pc]};
|
||||
@@ -106,16 +106,17 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::LD_L: {
|
||||
LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}",
|
||||
static_cast<u64>(instr.ld_l.unknown.Value()));
|
||||
|
||||
const auto GetLmem = [&](s32 offset) {
|
||||
case OpCode::Id::LD_L:
|
||||
LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown));
|
||||
[[fallthrough]];
|
||||
case OpCode::Id::LD_S: {
|
||||
const auto GetMemory = [&](s32 offset) {
|
||||
ASSERT(offset % 4 == 0);
|
||||
const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset);
|
||||
const Node address = Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8),
|
||||
immediate_offset);
|
||||
return GetLocalMemory(address);
|
||||
return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(address)
|
||||
: GetLocalMemory(address);
|
||||
};
|
||||
|
||||
switch (instr.ldst_sl.type.Value()) {
|
||||
@@ -135,14 +136,16 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
return 0;
|
||||
}
|
||||
}();
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
SetTemporary(bb, i, GetLmem(i * 4));
|
||||
for (u32 i = 0; i < count; ++i)
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
SetTemporary(bb, i, GetMemory(i * 4));
|
||||
}
|
||||
for (u32 i = 0; i < count; ++i) {
|
||||
SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("LD_L Unhandled type: {}",
|
||||
UNIMPLEMENTED_MSG("{} Unhandled type: {}", opcode->get().GetName(),
|
||||
static_cast<u32>(instr.ldst_sl.type.Value()));
|
||||
}
|
||||
break;
|
||||
@@ -209,27 +212,34 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::ST_L: {
|
||||
case OpCode::Id::ST_L:
|
||||
LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}",
|
||||
static_cast<u64>(instr.st_l.cache_management.Value()));
|
||||
|
||||
const auto GetLmemAddr = [&](s32 offset) {
|
||||
[[fallthrough]];
|
||||
case OpCode::Id::ST_S: {
|
||||
const auto GetAddress = [&](s32 offset) {
|
||||
ASSERT(offset % 4 == 0);
|
||||
const Node immediate = Immediate(static_cast<s32>(instr.smem_imm) + offset);
|
||||
return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate);
|
||||
};
|
||||
|
||||
const auto set_memory = opcode->get().GetId() == OpCode::Id::ST_L
|
||||
? &ShaderIR::SetLocalMemory
|
||||
: &ShaderIR::SetSharedMemory;
|
||||
|
||||
switch (instr.ldst_sl.type.Value()) {
|
||||
case Tegra::Shader::StoreType::Bits128:
|
||||
SetLocalMemory(bb, GetLmemAddr(12), GetRegister(instr.gpr0.Value() + 3));
|
||||
SetLocalMemory(bb, GetLmemAddr(8), GetRegister(instr.gpr0.Value() + 2));
|
||||
(this->*set_memory)(bb, GetAddress(12), GetRegister(instr.gpr0.Value() + 3));
|
||||
(this->*set_memory)(bb, GetAddress(8), GetRegister(instr.gpr0.Value() + 2));
|
||||
[[fallthrough]];
|
||||
case Tegra::Shader::StoreType::Bits64:
|
||||
SetLocalMemory(bb, GetLmemAddr(4), GetRegister(instr.gpr0.Value() + 1));
|
||||
(this->*set_memory)(bb, GetAddress(4), GetRegister(instr.gpr0.Value() + 1));
|
||||
[[fallthrough]];
|
||||
case Tegra::Shader::StoreType::Bits32:
|
||||
SetLocalMemory(bb, GetLmemAddr(0), GetRegister(instr.gpr0));
|
||||
(this->*set_memory)(bb, GetAddress(0), GetRegister(instr.gpr0));
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("ST_L Unhandled type: {}",
|
||||
UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(),
|
||||
static_cast<u32>(instr.ldst_sl.type.Value()));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace VideoCommon::Shader {
|
||||
using Tegra::Shader::Instruction;
|
||||
using Tegra::Shader::OpCode;
|
||||
using Tegra::Shader::Pred;
|
||||
using Tegra::Shader::ShuffleOperation;
|
||||
using Tegra::Shader::VoteOperation;
|
||||
|
||||
namespace {
|
||||
@@ -44,6 +45,52 @@ u32 ShaderIR::DecodeWarp(NodeBlock& bb, u32 pc) {
|
||||
SetPredicate(bb, instr.vote.dest_pred, vote);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SHFL: {
|
||||
Node mask = instr.shfl.is_mask_imm ? Immediate(static_cast<u32>(instr.shfl.mask_imm))
|
||||
: GetRegister(instr.gpr39);
|
||||
Node width = [&] {
|
||||
// Convert the obscure SHFL mask back into GL_NV_shader_thread_shuffle's width. This has
|
||||
// been done reversing Nvidia's math. It won't work on all cases due to SHFL having
|
||||
// different parameters that don't properly map to GLSL's interface, but it should work
|
||||
// for cases emitted by Nvidia's compiler.
|
||||
if (instr.shfl.operation == ShuffleOperation::Up) {
|
||||
return Operation(
|
||||
OperationCode::ILogicalShiftRight,
|
||||
Operation(OperationCode::IAdd, std::move(mask), Immediate(-0x2000)),
|
||||
Immediate(8));
|
||||
} else {
|
||||
return Operation(OperationCode::ILogicalShiftRight,
|
||||
Operation(OperationCode::IAdd, Immediate(0x201F),
|
||||
Operation(OperationCode::INegate, std::move(mask))),
|
||||
Immediate(8));
|
||||
}
|
||||
}();
|
||||
|
||||
const auto [operation, in_range] = [instr]() -> std::pair<OperationCode, OperationCode> {
|
||||
switch (instr.shfl.operation) {
|
||||
case ShuffleOperation::Idx:
|
||||
return {OperationCode::ShuffleIndexed, OperationCode::InRangeShuffleIndexed};
|
||||
case ShuffleOperation::Up:
|
||||
return {OperationCode::ShuffleUp, OperationCode::InRangeShuffleUp};
|
||||
case ShuffleOperation::Down:
|
||||
return {OperationCode::ShuffleDown, OperationCode::InRangeShuffleDown};
|
||||
case ShuffleOperation::Bfly:
|
||||
return {OperationCode::ShuffleButterfly, OperationCode::InRangeShuffleButterfly};
|
||||
}
|
||||
UNREACHABLE_MSG("Invalid SHFL operation: {}",
|
||||
static_cast<u64>(instr.shfl.operation.Value()));
|
||||
return {};
|
||||
}();
|
||||
|
||||
// Setting the predicate before the register is intentional to avoid overwriting.
|
||||
Node index = instr.shfl.is_index_imm ? Immediate(static_cast<u32>(instr.shfl.index_imm))
|
||||
: GetRegister(instr.gpr20);
|
||||
SetPredicate(bb, instr.shfl.pred48, Operation(in_range, index, width));
|
||||
SetRegister(
|
||||
bb, instr.gpr0,
|
||||
Operation(operation, GetRegister(instr.gpr8), std::move(index), std::move(width)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled warp instruction: {}", opcode->get().GetName());
|
||||
break;
|
||||
|
||||
@@ -181,6 +181,16 @@ enum class OperationCode {
|
||||
VoteAny, /// (bool) -> bool
|
||||
VoteEqual, /// (bool) -> bool
|
||||
|
||||
ShuffleIndexed, /// (uint value, uint index, uint width) -> uint
|
||||
ShuffleUp, /// (uint value, uint index, uint width) -> uint
|
||||
ShuffleDown, /// (uint value, uint index, uint width) -> uint
|
||||
ShuffleButterfly, /// (uint value, uint index, uint width) -> uint
|
||||
|
||||
InRangeShuffleIndexed, /// (uint index, uint width) -> bool
|
||||
InRangeShuffleUp, /// (uint index, uint width) -> bool
|
||||
InRangeShuffleDown, /// (uint index, uint width) -> bool
|
||||
InRangeShuffleButterfly, /// (uint index, uint width) -> bool
|
||||
|
||||
Amount,
|
||||
};
|
||||
|
||||
@@ -206,12 +216,13 @@ class PredicateNode;
|
||||
class AbufNode;
|
||||
class CbufNode;
|
||||
class LmemNode;
|
||||
class SmemNode;
|
||||
class GmemNode;
|
||||
class CommentNode;
|
||||
|
||||
using NodeData =
|
||||
std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, InternalFlagNode,
|
||||
PredicateNode, AbufNode, CbufNode, LmemNode, GmemNode, CommentNode>;
|
||||
PredicateNode, AbufNode, CbufNode, LmemNode, SmemNode, GmemNode, CommentNode>;
|
||||
using Node = std::shared_ptr<NodeData>;
|
||||
using Node4 = std::array<Node, 4>;
|
||||
using NodeBlock = std::vector<Node>;
|
||||
@@ -583,6 +594,19 @@ private:
|
||||
Node address;
|
||||
};
|
||||
|
||||
/// Shared memory node
|
||||
class SmemNode final {
|
||||
public:
|
||||
explicit SmemNode(Node address) : address{std::move(address)} {}
|
||||
|
||||
const Node& GetAddress() const {
|
||||
return address;
|
||||
}
|
||||
|
||||
private:
|
||||
Node address;
|
||||
};
|
||||
|
||||
/// Global memory node
|
||||
class GmemNode final {
|
||||
public:
|
||||
|
||||
@@ -137,6 +137,10 @@ Node ShaderIR::GetLocalMemory(Node address) {
|
||||
return MakeNode<LmemNode>(std::move(address));
|
||||
}
|
||||
|
||||
Node ShaderIR::GetSharedMemory(Node address) {
|
||||
return MakeNode<SmemNode>(std::move(address));
|
||||
}
|
||||
|
||||
Node ShaderIR::GetTemporary(u32 id) {
|
||||
return GetRegister(Register::ZeroIndex + 1 + id);
|
||||
}
|
||||
@@ -378,6 +382,11 @@ void ShaderIR::SetLocalMemory(NodeBlock& bb, Node address, Node value) {
|
||||
Operation(OperationCode::Assign, GetLocalMemory(std::move(address)), std::move(value)));
|
||||
}
|
||||
|
||||
void ShaderIR::SetSharedMemory(NodeBlock& bb, Node address, Node value) {
|
||||
bb.push_back(
|
||||
Operation(OperationCode::Assign, GetSharedMemory(std::move(address)), std::move(value)));
|
||||
}
|
||||
|
||||
void ShaderIR::SetTemporary(NodeBlock& bb, u32 id, Node value) {
|
||||
SetRegister(bb, Register::ZeroIndex + 1 + id, std::move(value));
|
||||
}
|
||||
|
||||
@@ -208,6 +208,8 @@ private:
|
||||
Node GetInternalFlag(InternalFlag flag, bool negated = false);
|
||||
/// Generates a node representing a local memory address
|
||||
Node GetLocalMemory(Node address);
|
||||
/// Generates a node representing a shared memory address
|
||||
Node GetSharedMemory(Node address);
|
||||
/// Generates a temporary, internally it uses a post-RZ register
|
||||
Node GetTemporary(u32 id);
|
||||
|
||||
@@ -217,8 +219,10 @@ private:
|
||||
void SetPredicate(NodeBlock& bb, u64 dest, Node src);
|
||||
/// Sets an internal flag. src value must be a bool-evaluated node
|
||||
void SetInternalFlag(NodeBlock& bb, InternalFlag flag, Node value);
|
||||
/// Sets a local memory address. address and value must be a number-evaluated node
|
||||
/// Sets a local memory address with a value.
|
||||
void SetLocalMemory(NodeBlock& bb, Node address, Node value);
|
||||
/// Sets a shared memory address with a value.
|
||||
void SetSharedMemory(NodeBlock& bb, Node address, Node value);
|
||||
/// Sets a temporary. Internally it uses a post-RZ register
|
||||
void SetTemporary(NodeBlock& bb, u32 id, Node value);
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ add_executable(yuzu
|
||||
configuration/configure_debug.ui
|
||||
configuration/configure_dialog.cpp
|
||||
configuration/configure_dialog.h
|
||||
configuration/configure_filesystem.cpp
|
||||
configuration/configure_filesystem.h
|
||||
configuration/configure_filesystem.ui
|
||||
configuration/configure_gamelist.cpp
|
||||
configuration/configure_gamelist.h
|
||||
configuration/configure_gamelist.ui
|
||||
|
||||
@@ -459,6 +459,49 @@ void Config::ReadDataStorageValues() {
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)))
|
||||
.toString()
|
||||
.toStdString());
|
||||
FileUtil::GetUserPath(
|
||||
FileUtil::UserPath::LoadDir,
|
||||
qt_config
|
||||
->value(QStringLiteral("load_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)))
|
||||
.toString()
|
||||
.toStdString());
|
||||
FileUtil::GetUserPath(
|
||||
FileUtil::UserPath::DumpDir,
|
||||
qt_config
|
||||
->value(QStringLiteral("dump_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)))
|
||||
.toString()
|
||||
.toStdString());
|
||||
FileUtil::GetUserPath(
|
||||
FileUtil::UserPath::CacheDir,
|
||||
qt_config
|
||||
->value(QStringLiteral("cache_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)))
|
||||
.toString()
|
||||
.toStdString());
|
||||
Settings::values.gamecard_inserted =
|
||||
ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
|
||||
Settings::values.gamecard_current_game =
|
||||
ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool();
|
||||
Settings::values.gamecard_path =
|
||||
ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString();
|
||||
Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
|
||||
ReadSetting(QStringLiteral("nand_total_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)))
|
||||
.toULongLong());
|
||||
Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
|
||||
ReadSetting(QStringLiteral("nand_user_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)))
|
||||
.toULongLong());
|
||||
Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
|
||||
ReadSetting(QStringLiteral("nand_system_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)))
|
||||
.toULongLong());
|
||||
Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
|
||||
ReadSetting(QStringLiteral("sdmc_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)))
|
||||
.toULongLong());
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
@@ -466,6 +509,9 @@ void Config::ReadDataStorageValues() {
|
||||
void Config::ReadDebuggingValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Debugging"));
|
||||
|
||||
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
||||
Settings::values.record_frame_times =
|
||||
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
||||
Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
|
||||
Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
|
||||
Settings::values.program_args =
|
||||
@@ -872,13 +918,40 @@ void Config::SaveDataStorageValues() {
|
||||
WriteSetting(QStringLiteral("sdmc_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
|
||||
WriteSetting(QStringLiteral("load_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
|
||||
WriteSetting(QStringLiteral("dump_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
|
||||
WriteSetting(QStringLiteral("cache_directory"),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
|
||||
WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
|
||||
WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
|
||||
false);
|
||||
WriteSetting(QStringLiteral("gamecard_path"),
|
||||
QString::fromStdString(Settings::values.gamecard_path), QStringLiteral(""));
|
||||
WriteSetting(QStringLiteral("nand_total_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB)));
|
||||
WriteSetting(QStringLiteral("nand_user_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB)));
|
||||
WriteSetting(QStringLiteral("nand_system_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB)));
|
||||
WriteSetting(QStringLiteral("sdmc_size"),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)),
|
||||
QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB)));
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::SaveDebuggingValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Debugging"));
|
||||
|
||||
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
||||
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
||||
WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
|
||||
WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
|
||||
WriteSetting(QStringLiteral("program_args"),
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
<string>Profiles</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureFilesystem" name="filesystemTab">
|
||||
<attribute name="title">
|
||||
<string>Filesystem</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureInputSimple" name="inputTab">
|
||||
<attribute name="title">
|
||||
<string>Input</string>
|
||||
@@ -125,6 +130,12 @@
|
||||
<header>configuration/configure_profile_manager.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureFilesystem</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_filesystem.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureAudio</class>
|
||||
<extends>QWidget</extends>
|
||||
|
||||
@@ -34,8 +34,6 @@ void ConfigureDebug::SetConfiguration() {
|
||||
ui->toggle_console->setChecked(UISettings::values.show_console);
|
||||
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
|
||||
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
|
||||
ui->dump_exefs->setChecked(Settings::values.dump_exefs);
|
||||
ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
|
||||
ui->reporting_services->setChecked(Settings::values.reporting_services);
|
||||
ui->quest_flag->setChecked(Settings::values.quest_flag);
|
||||
}
|
||||
@@ -46,8 +44,6 @@ void ConfigureDebug::ApplyConfiguration() {
|
||||
UISettings::values.show_console = ui->toggle_console->isChecked();
|
||||
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
|
||||
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
|
||||
Settings::values.dump_exefs = ui->dump_exefs->isChecked();
|
||||
Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked();
|
||||
Settings::values.reporting_services = ui->reporting_services->isChecked();
|
||||
Settings::values.quest_flag = ui->quest_flag->isChecked();
|
||||
Debugger::ToggleConsole();
|
||||
|
||||
@@ -103,58 +103,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Homebrew</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Arguments String</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="homebrew_args_edit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Dump</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="dump_decompressed_nso">
|
||||
<property name="whatsThis">
|
||||
<string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dump Decompressed NSOs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="dump_exefs">
|
||||
<property name="whatsThis">
|
||||
<string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dump ExeFS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="reporting_services">
|
||||
<property name="text">
|
||||
@@ -163,7 +111,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
@@ -196,11 +144,37 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Homebrew</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Arguments String</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="homebrew_args_edit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
|
||||
@@ -37,6 +37,7 @@ void ConfigureDialog::ApplyConfiguration() {
|
||||
ui->gameListTab->ApplyConfiguration();
|
||||
ui->systemTab->ApplyConfiguration();
|
||||
ui->profileManagerTab->ApplyConfiguration();
|
||||
ui->filesystemTab->applyConfiguration();
|
||||
ui->inputTab->ApplyConfiguration();
|
||||
ui->hotkeysTab->ApplyConfiguration(registry);
|
||||
ui->graphicsTab->ApplyConfiguration();
|
||||
@@ -73,7 +74,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
|
||||
void ConfigureDialog::PopulateSelectionList() {
|
||||
const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
|
||||
{{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
|
||||
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}},
|
||||
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}},
|
||||
{tr("Graphics"), {ui->graphicsTab}},
|
||||
{tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
|
||||
};
|
||||
@@ -106,6 +107,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{ui->debugTab, tr("Debug")},
|
||||
{ui->webTab, tr("Web")},
|
||||
{ui->gameListTab, tr("Game List")},
|
||||
{ui->filesystemTab, tr("Filesystem")},
|
||||
};
|
||||
|
||||
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_filesystem.h"
|
||||
#include "yuzu/configuration/configure_filesystem.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void SetComboBoxFromData(QComboBox* combo_box, T data) {
|
||||
const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data)));
|
||||
if (index >= combo_box->count() || index < 0)
|
||||
return;
|
||||
|
||||
combo_box->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {
|
||||
ui->setupUi(this);
|
||||
this->setConfiguration();
|
||||
|
||||
connect(ui->nand_directory_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); });
|
||||
connect(ui->sdmc_directory_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); });
|
||||
connect(ui->gamecard_path_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); });
|
||||
connect(ui->dump_path_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });
|
||||
connect(ui->load_path_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); });
|
||||
connect(ui->cache_directory_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); });
|
||||
|
||||
connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
|
||||
&ConfigureFilesystem::ResetMetadata);
|
||||
|
||||
connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this,
|
||||
&ConfigureFilesystem::UpdateEnabledControls);
|
||||
connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this,
|
||||
&ConfigureFilesystem::UpdateEnabledControls);
|
||||
}
|
||||
|
||||
ConfigureFilesystem::~ConfigureFilesystem() = default;
|
||||
|
||||
void ConfigureFilesystem::setConfiguration() {
|
||||
ui->nand_directory_edit->setText(
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
|
||||
ui->sdmc_directory_edit->setText(
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
|
||||
ui->dump_path_edit->setText(
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
|
||||
ui->load_path_edit->setText(
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
|
||||
ui->cache_directory_edit->setText(
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
|
||||
|
||||
ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
|
||||
ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
|
||||
ui->dump_exefs->setChecked(Settings::values.dump_exefs);
|
||||
ui->dump_nso->setChecked(Settings::values.dump_nso);
|
||||
|
||||
ui->cache_game_list->setChecked(UISettings::values.cache_game_list);
|
||||
|
||||
SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size);
|
||||
SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size);
|
||||
SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size);
|
||||
SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size);
|
||||
|
||||
UpdateEnabledControls();
|
||||
}
|
||||
|
||||
void ConfigureFilesystem::applyConfiguration() {
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir,
|
||||
ui->nand_directory_edit->text().toStdString());
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
|
||||
ui->sdmc_directory_edit->text().toStdString());
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString());
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString());
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
|
||||
ui->cache_directory_edit->text().toStdString());
|
||||
Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
|
||||
|
||||
Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
|
||||
Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
|
||||
Settings::values.dump_exefs = ui->dump_exefs->isChecked();
|
||||
Settings::values.dump_nso = ui->dump_nso->isChecked();
|
||||
|
||||
UISettings::values.cache_game_list = ui->cache_game_list->isChecked();
|
||||
|
||||
Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(
|
||||
ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong());
|
||||
Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
|
||||
ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong());
|
||||
Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(
|
||||
ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong());
|
||||
Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(
|
||||
ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong());
|
||||
}
|
||||
|
||||
void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
|
||||
QString caption;
|
||||
|
||||
switch (target) {
|
||||
case DirectoryTarget::NAND:
|
||||
caption = tr("Select Emulated NAND Directory...");
|
||||
break;
|
||||
case DirectoryTarget::SD:
|
||||
caption = tr("Select Emulated SD Directory...");
|
||||
break;
|
||||
case DirectoryTarget::Gamecard:
|
||||
caption = tr("Select Gamecard Path...");
|
||||
break;
|
||||
case DirectoryTarget::Dump:
|
||||
caption = tr("Select Dump Directory...");
|
||||
break;
|
||||
case DirectoryTarget::Load:
|
||||
caption = tr("Select Mod Load Directory...");
|
||||
break;
|
||||
case DirectoryTarget::Cache:
|
||||
caption = tr("Select Cache Directory...");
|
||||
break;
|
||||
}
|
||||
|
||||
QString str;
|
||||
if (target == DirectoryTarget::Gamecard) {
|
||||
str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(),
|
||||
QStringLiteral("NX Gamecard;*.xci"));
|
||||
} else {
|
||||
str = QFileDialog::getExistingDirectory(this, caption, edit->text());
|
||||
}
|
||||
|
||||
if (str.isEmpty())
|
||||
return;
|
||||
|
||||
edit->setText(str);
|
||||
}
|
||||
|
||||
void ConfigureFilesystem::ResetMetadata() {
|
||||
if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
|
||||
"game_list")) {
|
||||
QMessageBox::information(this, tr("Reset Metadata Cache"),
|
||||
tr("The metadata cache is already empty."));
|
||||
} else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
||||
DIR_SEP + "game_list")) {
|
||||
QMessageBox::information(this, tr("Reset Metadata Cache"),
|
||||
tr("The operation completed successfully."));
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this, tr("Reset Metadata Cache"),
|
||||
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureFilesystem::UpdateEnabledControls() {
|
||||
ui->gamecard_current_game->setEnabled(ui->gamecard_inserted->isChecked());
|
||||
ui->gamecard_path_edit->setEnabled(ui->gamecard_inserted->isChecked() &&
|
||||
!ui->gamecard_current_game->isChecked());
|
||||
ui->gamecard_path_button->setEnabled(ui->gamecard_inserted->isChecked() &&
|
||||
!ui->gamecard_current_game->isChecked());
|
||||
}
|
||||
|
||||
void ConfigureFilesystem::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QWidget>
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureFilesystem;
|
||||
}
|
||||
|
||||
class ConfigureFilesystem : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureFilesystem(QWidget* parent = nullptr);
|
||||
~ConfigureFilesystem() override;
|
||||
|
||||
void applyConfiguration();
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
|
||||
enum class DirectoryTarget {
|
||||
NAND,
|
||||
SD,
|
||||
Gamecard,
|
||||
Dump,
|
||||
Load,
|
||||
Cache,
|
||||
};
|
||||
|
||||
void SetDirectory(DirectoryTarget target, QLineEdit* edit);
|
||||
void ResetMetadata();
|
||||
void UpdateEnabledControls();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureFilesystem> ui;
|
||||
};
|
||||
@@ -0,0 +1,395 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureFilesystem</class>
|
||||
<widget class="QWidget" name="ConfigureFilesystem">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>453</width>
|
||||
<height>561</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Storage Directories</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>NAND</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="nand_directory_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="nand_directory_edit"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="sdmc_directory_edit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>SD Card</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QToolButton" name="sdmc_directory_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Maximum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Gamecard</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLineEdit" name="gamecard_path_edit"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="gamecard_inserted">
|
||||
<property name="text">
|
||||
<string>Inserted</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="gamecard_current_game">
|
||||
<property name="text">
|
||||
<string>Current Game</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QToolButton" name="gamecard_path_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Storage Sizes</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>SD Card</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>System NAND</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="sysnand_size">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2.5 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="sdmc_size">
|
||||
<property name="currentText">
|
||||
<string>32 GB</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>8 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>16 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>32 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>64 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>128 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>256 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1 TB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="usrnand_size">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>26 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>User NAND</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>NAND</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="nand_size">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>29.1 GB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Patch Manager</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="load_path_edit"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="dump_path_edit"/>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="dump_path_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QToolButton" name="load_path_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="dump_nso">
|
||||
<property name="text">
|
||||
<string>Dump Decompressed NSOs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="dump_exefs">
|
||||
<property name="text">
|
||||
<string>Dump ExeFS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Mod Load Root</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Dump Root</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Caching</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Cache Directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="cache_directory_edit"/>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QToolButton" name="cache_directory_button">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="4">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cache_game_list">
|
||||
<property name="text">
|
||||
<string>Cache Game List Metadata</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="reset_game_list_cache">
|
||||
<property name="text">
|
||||
<string>Reset Metadata Cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QSpinBox>
|
||||
#include "core/core.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_general.h"
|
||||
|
||||
@@ -182,6 +182,8 @@ void ConfigureInput::UpdateUIEnabled() {
|
||||
players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);
|
||||
}
|
||||
|
||||
ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() &&
|
||||
!ui->use_docked_mode->isChecked());
|
||||
ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked());
|
||||
ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() &&
|
||||
!ui->use_docked_mode->isChecked());
|
||||
|
||||
+38
-17
@@ -54,6 +54,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include <QProgressDialog>
|
||||
#include <QShortcut>
|
||||
#include <QStatusBar>
|
||||
#include <QSysInfo>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include <fmt/format.h>
|
||||
@@ -66,6 +67,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
@@ -205,6 +209,10 @@ GMainWindow::GMainWindow()
|
||||
|
||||
LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
LOG_INFO(Frontend, "Host CPU: {}", Common::GetCPUCaps().cpu_string);
|
||||
#endif
|
||||
LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString());
|
||||
UpdateWindowTitle();
|
||||
|
||||
show();
|
||||
@@ -213,7 +221,7 @@ GMainWindow::GMainWindow()
|
||||
std::make_unique<FileSys::ContentProviderUnion>());
|
||||
Core::System::GetInstance().RegisterContentProvider(
|
||||
FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
|
||||
Service::FileSystem::CreateFactories(*vfs);
|
||||
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
|
||||
|
||||
// Gen keys if necessary
|
||||
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
|
||||
@@ -964,11 +972,11 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
}
|
||||
status_bar_update_timer.start(2000);
|
||||
|
||||
const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
|
||||
std::string title_name;
|
||||
const auto res = Core::System::GetInstance().GetGameName(title_name);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
|
||||
const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata();
|
||||
if (nacp != nullptr)
|
||||
title_name = nacp->GetApplicationName();
|
||||
@@ -976,7 +984,7 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
if (title_name.empty())
|
||||
title_name = FileUtil::GetFilename(filename.toStdString());
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name);
|
||||
UpdateWindowTitle(QString::fromStdString(title_name));
|
||||
|
||||
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
|
||||
@@ -1499,15 +1507,19 @@ void GMainWindow::OnMenuInstallToNAND() {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
const auto res =
|
||||
Service::FileSystem::GetUserNANDContents()->InstallEntry(*nsp, false, qt_raw_copy);
|
||||
const auto res = Core::System::GetInstance()
|
||||
.GetFileSystemController()
|
||||
.GetUserNANDContents()
|
||||
->InstallEntry(*nsp, false, qt_raw_copy);
|
||||
if (res == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else {
|
||||
if (res == FileSys::InstallResult::ErrorAlreadyExists) {
|
||||
if (overwrite()) {
|
||||
const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
|
||||
*nsp, true, qt_raw_copy);
|
||||
const auto res2 = Core::System::GetInstance()
|
||||
.GetFileSystemController()
|
||||
.GetUserNANDContents()
|
||||
->InstallEntry(*nsp, true, qt_raw_copy);
|
||||
if (res2 == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else {
|
||||
@@ -1561,19 +1573,28 @@ void GMainWindow::OnMenuInstallToNAND() {
|
||||
|
||||
FileSys::InstallResult res;
|
||||
if (index >= static_cast<size_t>(FileSys::TitleType::Application)) {
|
||||
res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
|
||||
*nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
|
||||
res = Core::System::GetInstance()
|
||||
.GetFileSystemController()
|
||||
.GetUserNANDContents()
|
||||
->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
|
||||
qt_raw_copy);
|
||||
} else {
|
||||
res = Service::FileSystem::GetSystemNANDContents()->InstallEntry(
|
||||
*nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
|
||||
res = Core::System::GetInstance()
|
||||
.GetFileSystemController()
|
||||
.GetSystemNANDContents()
|
||||
->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);
|
||||
const auto res2 = Core::System::GetInstance()
|
||||
.GetFileSystemController()
|
||||
.GetUserNANDContents()
|
||||
->InstallEntry(*nca, static_cast<FileSys::TitleType>(index),
|
||||
true, qt_raw_copy);
|
||||
if (res2 == FileSys::InstallResult::Success) {
|
||||
success();
|
||||
} else {
|
||||
@@ -1603,7 +1624,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
|
||||
FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
|
||||
: FileUtil::UserPath::NANDDir,
|
||||
dir_path.toStdString());
|
||||
Service::FileSystem::CreateFactories(*vfs);
|
||||
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
}
|
||||
}
|
||||
@@ -1988,7 +2009,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||
|
||||
const auto function = [this, &keys, &pdm] {
|
||||
keys.PopulateFromPartitionData(pdm);
|
||||
Service::FileSystem::CreateFactories(*vfs);
|
||||
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
|
||||
keys.DeriveETicket(pdm);
|
||||
};
|
||||
|
||||
@@ -2033,7 +2054,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||
prog.close();
|
||||
}
|
||||
|
||||
Service::FileSystem::CreateFactories(*vfs);
|
||||
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
|
||||
|
||||
if (behavior == ReinitializeKeyBehavior::Warning) {
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
|
||||
@@ -316,6 +316,29 @@ void Config::ReadValues() {
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir,
|
||||
sdl2_config->Get("Data Storage", "sdmc_directory",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir,
|
||||
sdl2_config->Get("Data Storage", "load_directory",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)));
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir,
|
||||
sdl2_config->Get("Data Storage", "dump_directory",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)));
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::CacheDir,
|
||||
sdl2_config->Get("Data Storage", "cache_directory",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)));
|
||||
Settings::values.gamecard_inserted =
|
||||
sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
|
||||
Settings::values.gamecard_current_game =
|
||||
sdl2_config->GetBoolean("Data Storage", "gamecard_current_game", false);
|
||||
Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", "");
|
||||
Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(sdl2_config->GetInteger(
|
||||
"Data Storage", "nand_total_size", static_cast<long>(Settings::NANDTotalSize::S29_1GB)));
|
||||
Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(sdl2_config->GetInteger(
|
||||
"Data Storage", "nand_user_size", static_cast<long>(Settings::NANDUserSize::S26GB)));
|
||||
Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>(
|
||||
sdl2_config->GetInteger("Data Storage", "nand_system_size",
|
||||
static_cast<long>(Settings::NANDSystemSize::S2_5GB)));
|
||||
Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(sdl2_config->GetInteger(
|
||||
"Data Storage", "sdmc_size", static_cast<long>(Settings::SDMCSize::S16GB)));
|
||||
|
||||
// System
|
||||
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
|
||||
@@ -374,6 +397,8 @@ void Config::ReadValues() {
|
||||
Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);
|
||||
|
||||
// Debugging
|
||||
Settings::values.record_frame_times =
|
||||
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
|
||||
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
|
||||
Settings::values.gdbstub_port =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
||||
|
||||
@@ -173,6 +173,20 @@ volume =
|
||||
# 1 (default): Yes, 0: No
|
||||
use_virtual_sd =
|
||||
|
||||
# Whether or not to enable gamecard emulation
|
||||
# 1: Yes, 0 (default): No
|
||||
gamecard_inserted =
|
||||
|
||||
# Whether or not the gamecard should be emulated as the current game
|
||||
# If 'gamecard_inserted' is 0 this setting is irrelevant
|
||||
# 1: Yes, 0 (default): No
|
||||
gamecard_current_game =
|
||||
|
||||
# Path to an XCI file to use as the gamecard
|
||||
# If 'gamecard_inserted' is 0 this setting is irrelevant
|
||||
# If 'gamecard_current_game' is 1 this setting is irrelevant
|
||||
gamecard_path =
|
||||
|
||||
[System]
|
||||
# Whether the system is docked
|
||||
# 1: Yes, 0 (default): No
|
||||
@@ -213,6 +227,8 @@ region_value =
|
||||
log_filter = *:Trace
|
||||
|
||||
[Debugging]
|
||||
# Record frame time data, can be found in the log directory. Boolean value
|
||||
record_frame_times =
|
||||
# Port for listening to GDB connections.
|
||||
use_gdbstub=false
|
||||
gdbstub_port=24689
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include <SDL.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/core.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
@@ -170,6 +172,16 @@ void EmuWindow_SDL2::PollEvents() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const u32 current_time = SDL_GetTicks();
|
||||
if (current_time > last_time + 2000) {
|
||||
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
|
||||
const auto title = fmt::format(
|
||||
"yuzu {} | {}-{} | FPS: {:.0f} ({:.0%})", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc, results.game_fps, results.emulation_speed);
|
||||
SDL_SetWindowTitle(render_window, title.c_str());
|
||||
last_time = current_time;
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
|
||||
|
||||
@@ -60,4 +60,7 @@ protected:
|
||||
|
||||
/// Internal SDL2 render window
|
||||
SDL_Window* render_window;
|
||||
|
||||
/// Keeps track of how often to update the title bar during gameplay
|
||||
u32 last_time = 0;
|
||||
};
|
||||
|
||||
@@ -184,9 +184,7 @@ int main(int argc, char** argv) {
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
Service::FileSystem::CreateFactories(*system.GetFilesystem());
|
||||
|
||||
SCOPE_EXIT({ system.Shutdown(); });
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
||||
|
||||
const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
|
||||
|
||||
@@ -227,6 +225,8 @@ int main(int argc, char** argv) {
|
||||
system.RunLoop();
|
||||
}
|
||||
|
||||
system.Shutdown();
|
||||
|
||||
detached_tasks.WaitForAllTasks();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
@@ -216,8 +217,9 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
Service::FileSystem::CreateFactories(*system.GetFilesystem());
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
||||
|
||||
SCOPE_EXIT({ system.Shutdown(); });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user