mirror of
https://github.com/citron-neo/emulator.git
synced 2026-07-05 15:21:57 -07:00
Merge pull request #231 from Splaser/main
Fix filesystem/content lifecycle race causing inconsistent firmware and DLC state on relaunch and update install
This commit is contained in:
+1
-1
@@ -426,7 +426,7 @@ class HomeSettingsFragment : Fragment() {
|
||||
|
||||
binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation)
|
||||
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
if (view.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail)
|
||||
} else {
|
||||
binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail)
|
||||
|
||||
@@ -681,8 +681,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
|
||||
// Reinitialize relevant data
|
||||
NativeLibrary.initializeSystem(true)
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
NativeLibrary.initializeSystem(true)
|
||||
gamesViewModel.reloadGames(false)
|
||||
driverViewModel.reloadDriverData()
|
||||
|
||||
|
||||
+2
-1
@@ -23,9 +23,10 @@ object DirectoryInitialization {
|
||||
fun start() {
|
||||
if (!areDirectoriesReady) {
|
||||
initializeInternalStorage()
|
||||
NativeLibrary.initializeSystem(false)
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
migrateSettings()
|
||||
NativeLibrary.reloadKeys()
|
||||
NativeLibrary.initializeSystem(false)
|
||||
areDirectoriesReady = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,22 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeSystem(bool reload) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
if (m_is_running || m_load_result == Core::SystemResultStatus::Success) {
|
||||
LOG_ERROR(Frontend, "Refusing to initialize system while emulation is active");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& fs_controller = m_system.GetFileSystemController();
|
||||
if (fs_controller.GetInitStage() < Service::FileSystem::InitStage::SETTINGS_READY) {
|
||||
LOG_ERROR(Frontend, "Refusing to initialize system before settings are ready");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reload) {
|
||||
m_system.Initialize();
|
||||
|
||||
// Initialize logging system
|
||||
Common::Log::Initialize();
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
@@ -218,6 +233,11 @@ void EmulationSession::InitializeSystem(bool reload) {
|
||||
m_input_subsystem.Initialize();
|
||||
}
|
||||
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
Core::Crypto::KeyManager::Instance().PopulateTickets();
|
||||
LOG_INFO(Frontend, "InitializeSystem: keys_loaded_before_content={}",
|
||||
Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
||||
|
||||
// Initialize filesystem.
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
m_system.GetUserChannel().clear();
|
||||
@@ -225,7 +245,52 @@ void EmulationSession::InitializeSystem(bool reload) {
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
m_manual_provider.get());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
fs_controller.SetInitStage(Service::FileSystem::InitStage::FS_READY);
|
||||
fs_controller.InitializeContentSystem(*m_vfs);
|
||||
}
|
||||
|
||||
|
||||
void EmulationSession::RefreshContentSystemUnlocked()
|
||||
{
|
||||
auto& fs = m_system.GetFileSystemController();
|
||||
if (auto filesystem = m_system.GetFilesystem()) {
|
||||
fs.InitializeContentSystem(*filesystem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EmulationSession::RefreshContentSystem() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
RefreshContentSystemUnlocked();
|
||||
}
|
||||
|
||||
|
||||
bool EmulationSession::RefreshContentIfIdle(bool keys_loaded)
|
||||
{
|
||||
if (!keys_loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(m_mutex);
|
||||
if (m_is_running || m_load_result == Core::SystemResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RefreshContentSystemUnlocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmulationSession::SetFilesystemInitStage(Service::FileSystem::InitStage stage) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.GetFileSystemController().SetInitStage(stage);
|
||||
}
|
||||
|
||||
void EmulationSession::PromoteFilesystemInitStage(Service::FileSystem::InitStage stage) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
auto& controller = m_system.GetFileSystemController();
|
||||
if (controller.GetInitStage() < stage) {
|
||||
controller.SetInitStage(stage);
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::SetAppletId(int applet_id) {
|
||||
@@ -561,8 +626,16 @@ jobjectArray Java_org_citron_citron_1emu_utils_GpuDriverHelper_getSystemDriverIn
|
||||
}
|
||||
|
||||
jboolean Java_org_citron_citron_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {
|
||||
auto& session = EmulationSession::GetInstance();
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
||||
const bool keys_loaded = Core::Crypto::KeyManager::Instance().AreKeysLoaded();
|
||||
|
||||
const bool refreshed_content = session.RefreshContentIfIdle(keys_loaded);
|
||||
LOG_INFO(Frontend,
|
||||
"reloadKeys: keys_loaded={}, refreshed_content={}",
|
||||
keys_loaded, refreshed_content);
|
||||
|
||||
return static_cast<jboolean>(keys_loaded);
|
||||
}
|
||||
|
||||
void Java_org_citron_citron_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
@@ -588,9 +661,6 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass
|
||||
void Java_org_citron_citron_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
||||
jboolean reload) {
|
||||
// Initialize the emulated system.
|
||||
if (!reload) {
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
}
|
||||
EmulationSession::GetInstance().InitializeSystem(reload);
|
||||
}
|
||||
|
||||
@@ -893,9 +963,16 @@ void Java_org_citron_citron_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* e
|
||||
}
|
||||
|
||||
jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) {
|
||||
auto& system = EmulationSession::GetInstance().System();
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
||||
auto& session = EmulationSession::GetInstance();
|
||||
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
const bool keys_loaded =
|
||||
Core::Crypto::KeyManager::Instance().AreKeysLoaded();
|
||||
|
||||
const bool refreshed_content = session.RefreshContentIfIdle(keys_loaded);
|
||||
|
||||
LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content);
|
||||
|
||||
return ContentManager::AreKeysPresent();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "jni/emu_window/emu_window.h"
|
||||
@@ -46,6 +47,10 @@ public:
|
||||
const Core::PerfStatsResults& PerfStats();
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void InitializeSystem(bool reload);
|
||||
void RefreshContentSystem();
|
||||
bool RefreshContentIfIdle(bool keys_loaded);
|
||||
void SetFilesystemInitStage(Service::FileSystem::InitStage stage);
|
||||
void PromoteFilesystemInitStage(Service::FileSystem::InitStage stage);
|
||||
void SetAppletId(int applet_id);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
|
||||
const std::size_t program_index,
|
||||
@@ -58,6 +63,7 @@ public:
|
||||
static u64 GetProgramId(JNIEnv* env, jstring jprogramId);
|
||||
|
||||
private:
|
||||
void RefreshContentSystemUnlocked();
|
||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
||||
static void OnEmulationStopped(Core::SystemResultStatus result);
|
||||
static void ChangeProgram(std::size_t program_index);
|
||||
|
||||
@@ -12,11 +12,24 @@
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "native.h"
|
||||
|
||||
std::unique_ptr<AndroidConfig> global_config;
|
||||
std::unique_ptr<AndroidConfig> per_game_config;
|
||||
|
||||
namespace {
|
||||
|
||||
void SetFilesystemInitStage(Service::FileSystem::InitStage stage) {
|
||||
EmulationSession::GetInstance().SetFilesystemInitStage(stage);
|
||||
}
|
||||
|
||||
void PromoteFilesystemInitStage(Service::FileSystem::InitStage stage) {
|
||||
EmulationSession::GetInstance().PromoteFilesystemInitStage(stage);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename T>
|
||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||
auto key = Common::Android::GetJString(env, jkey);
|
||||
@@ -36,14 +49,17 @@ extern "C" {
|
||||
|
||||
void Java_org_citron_citron_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config = std::make_unique<AndroidConfig>();
|
||||
PromoteFilesystemInitStage(Service::FileSystem::InitStage::SETTINGS_READY);
|
||||
}
|
||||
|
||||
void Java_org_citron_citron_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config.reset();
|
||||
SetFilesystemInitStage(Service::FileSystem::InitStage::NONE);
|
||||
}
|
||||
|
||||
void Java_org_citron_citron_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
global_config->AndroidConfig::ReloadAllValues();
|
||||
PromoteFilesystemInitStage(Service::FileSystem::InitStage::SETTINGS_READY);
|
||||
}
|
||||
|
||||
void Java_org_citron_citron_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
||||
|
||||
+6
-6
@@ -452,7 +452,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
|
||||
|
||||
// 1. First, create the factories
|
||||
LOG_INFO(Frontend, "Initializing factories...");
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
system->GetFileSystemController().InitializeContentSystem(*vfs);
|
||||
|
||||
autoloader_provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::Autoloader,
|
||||
@@ -2556,7 +2556,7 @@ void GMainWindow::OnEmulationStopped() {
|
||||
game_list->CancelPopulation();
|
||||
|
||||
// This is necessary to reset the in-memory state for the next launch.
|
||||
system->GetFileSystemController().CreateFactories(*vfs, true);
|
||||
system->GetFileSystemController().InitializeContentSystem(*vfs, true);
|
||||
|
||||
// Refresh the game list now that the filesystem is valid again.
|
||||
game_list->ClearLaunchOverlays();
|
||||
@@ -5130,7 +5130,7 @@ void GMainWindow::OnInstallFirmwareFromZip() {
|
||||
}
|
||||
|
||||
// Re-scan VFS for the newly placed firmware files.
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
system->GetFileSystemController().InitializeContentSystem(*vfs);
|
||||
|
||||
auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(85 + static_cast<int>((processed_size * 15) / total_size));
|
||||
@@ -5294,7 +5294,7 @@ void GMainWindow::OnInstallFirmware() {
|
||||
}
|
||||
|
||||
// Re-scan VFS for the newly placed firmware files.
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
system->GetFileSystemController().InitializeContentSystem(*vfs);
|
||||
|
||||
auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size));
|
||||
@@ -5391,7 +5391,7 @@ void GMainWindow::OnInstallDecryptionKeys() {
|
||||
// and re-populate the game list in the UI if the user has already added
|
||||
// game folders.
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
system->GetFileSystemController().InitializeContentSystem(*vfs);
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
|
||||
if (ContentManager::AreKeysPresent()) {
|
||||
@@ -6066,7 +6066,7 @@ void GMainWindow::OnMouseActivity() {
|
||||
}
|
||||
|
||||
void GMainWindow::OnCheckFirmwareDecryption() {
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
system->GetFileSystemController().InitializeContentSystem(*vfs);
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::warning(this, tr("Derivation Components Missing"),
|
||||
tr("Encryption keys are missing. "
|
||||
|
||||
@@ -360,7 +360,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
|
||||
system.GetFileSystemController().InitializeContentSystem(*system.GetFilesystem());
|
||||
system.GetUserChannel().clear();
|
||||
|
||||
Service::AM::FrontendAppletParameters load_parameters{
|
||||
|
||||
@@ -234,18 +234,33 @@ Ticket Ticket::Read(std::span<const u8> raw_data) {
|
||||
switch (sig_type) {
|
||||
case SignatureType::RSA_4096_SHA1:
|
||||
case SignatureType::RSA_4096_SHA256: {
|
||||
if (raw_data.size() < sizeof(RSA4096Ticket)) {
|
||||
LOG_WARNING(Crypto, "Attempted to parse RSA4096 ticket buffer with invalid size {}.",
|
||||
raw_data.size());
|
||||
return Ticket{std::monostate()};
|
||||
}
|
||||
RSA4096Ticket ticket{};
|
||||
std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
|
||||
return Ticket{ticket};
|
||||
}
|
||||
case SignatureType::RSA_2048_SHA1:
|
||||
case SignatureType::RSA_2048_SHA256: {
|
||||
if (raw_data.size() < sizeof(RSA2048Ticket)) {
|
||||
LOG_WARNING(Crypto, "Attempted to parse RSA2048 ticket buffer with invalid size {}.",
|
||||
raw_data.size());
|
||||
return Ticket{std::monostate()};
|
||||
}
|
||||
RSA2048Ticket ticket{};
|
||||
std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
|
||||
return Ticket{ticket};
|
||||
}
|
||||
case SignatureType::ECDSA_SHA1:
|
||||
case SignatureType::ECDSA_SHA256: {
|
||||
if (raw_data.size() < sizeof(ECDSATicket)) {
|
||||
LOG_WARNING(Crypto, "Attempted to parse ECDSA ticket buffer with invalid size {}.",
|
||||
raw_data.size());
|
||||
return Ticket{std::monostate()};
|
||||
}
|
||||
ECDSATicket ticket{};
|
||||
std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
|
||||
return Ticket{ticket};
|
||||
@@ -479,6 +494,22 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
static std::optional<std::size_t> GetTicketSize(SignatureType sig_type) {
|
||||
switch (sig_type) {
|
||||
case SignatureType::RSA_4096_SHA1:
|
||||
case SignatureType::RSA_4096_SHA256:
|
||||
return sizeof(RSA4096Ticket);
|
||||
case SignatureType::RSA_2048_SHA1:
|
||||
case SignatureType::RSA_2048_SHA256:
|
||||
return sizeof(RSA2048Ticket);
|
||||
case SignatureType::ECDSA_SHA1:
|
||||
case SignatureType::ECDSA_SHA256:
|
||||
return sizeof(ECDSATicket);
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
|
||||
if (!ticket_save.IsOpen()) {
|
||||
return {};
|
||||
@@ -490,21 +521,46 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
|
||||
}
|
||||
|
||||
std::vector<Ticket> out;
|
||||
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
|
||||
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
|
||||
buffer[offset + 3] == 0x0) {
|
||||
// NOTE: Assumes ticket blob will only contain RSA-2048 tickets.
|
||||
auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)});
|
||||
offset += sizeof(RSA2048Ticket);
|
||||
if (ticket.IsValid()) {
|
||||
out.push_back(ticket);
|
||||
}
|
||||
for (std::size_t offset = 0; offset + sizeof(SignatureType) <= buffer.size(); ++offset) {
|
||||
SignatureType sig_type;
|
||||
std::memcpy(&sig_type, buffer.data() + offset, sizeof(sig_type));
|
||||
|
||||
const auto ticket_size = GetTicketSize(sig_type);
|
||||
if (!ticket_size) {
|
||||
continue;
|
||||
}
|
||||
if (offset + *ticket_size > buffer.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto ticket = Ticket::Read(std::span{buffer.data() + offset, *ticket_size});
|
||||
offset += *ticket_size - 1;
|
||||
if (ticket.IsValid()) {
|
||||
out.push_back(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::span<const u8> GetTicketBytes(const Ticket& ticket) {
|
||||
if (const auto* rsa4096 = std::get_if<RSA4096Ticket>(&ticket.data)) {
|
||||
return {reinterpret_cast<const u8*>(rsa4096), sizeof(RSA4096Ticket)};
|
||||
}
|
||||
if (const auto* rsa2048 = std::get_if<RSA2048Ticket>(&ticket.data)) {
|
||||
return {reinterpret_cast<const u8*>(rsa2048), sizeof(RSA2048Ticket)};
|
||||
}
|
||||
if (const auto* ecdsa = std::get_if<ECDSATicket>(&ticket.data)) {
|
||||
return {reinterpret_cast<const u8*>(ecdsa), sizeof(ECDSATicket)};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool TicketMatchesRightsId(const Ticket& ticket, const std::array<u8, 0x10>& rights_id) {
|
||||
return ticket.IsValid() && ticket.GetData().rights_id == rights_id;
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
|
||||
const std::array<u8, size>& rhs) {
|
||||
@@ -1282,4 +1338,53 @@ bool KeyManager::AddTicket(const Ticket& ticket) {
|
||||
SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KeyManager::PersistTicket(const Ticket& ticket) {
|
||||
if (!ticket.IsValid()) {
|
||||
LOG_WARNING(Crypto, "Attempted to persist invalid ticket.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto ticket_path = Common::FS::GetCitronPath(Common::FS::CitronPath::NANDDir) /
|
||||
(ticket.GetData().type == TitleKeyType::Common
|
||||
? "system/save/80000000000000e1"
|
||||
: "system/save/80000000000000e2");
|
||||
if (!Common::FS::CreateParentDirs(ticket_path)) {
|
||||
LOG_ERROR(Crypto, "Failed to create ticket save directory for {}",
|
||||
Common::FS::PathToUTF8String(ticket_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Common::FS::Exists(ticket_path)) {
|
||||
const Common::FS::IOFile existing_file{ticket_path, Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
const auto existing_tickets = GetTicketblob(existing_file);
|
||||
if (std::any_of(existing_tickets.begin(), existing_tickets.end(),
|
||||
[&ticket](const Ticket& existing_ticket) {
|
||||
return TicketMatchesRightsId(existing_ticket,
|
||||
ticket.GetData().rights_id);
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto ticket_bytes = GetTicketBytes(ticket);
|
||||
if (ticket_bytes.empty()) {
|
||||
LOG_WARNING(Crypto, "Unable to serialize invalid ticket variant.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Common::FS::IOFile ticket_save{ticket_path, Common::FS::FileAccessMode::Append,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
if (!ticket_save.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ticket_save.WriteSpan(ticket_bytes) != ticket_bytes.size()) {
|
||||
LOG_ERROR(Crypto, "Failed to write ticket to {}", Common::FS::PathToUTF8String(ticket_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
return ticket_save.Flush();
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -291,6 +291,7 @@ public:
|
||||
const std::map<u128, Ticket>& GetPersonalizedTickets() const;
|
||||
|
||||
bool AddTicket(const Ticket& ticket);
|
||||
bool PersistTicket(const Ticket& ticket);
|
||||
|
||||
void ReloadKeys();
|
||||
bool AreKeysLoaded() const;
|
||||
|
||||
+141
-134
@@ -46,6 +46,12 @@
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
// patch_manager.cpp only
|
||||
const ContentProviderUnion* GetUnionProvider(const ContentProvider& p) {
|
||||
return p.IsUnionProvider()
|
||||
? static_cast<const ContentProviderUnion*>(&p)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
constexpr u32 SINGLE_BYTE_MODULUS = 0x100;
|
||||
|
||||
@@ -172,6 +178,8 @@ bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
|
||||
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
const Service::FileSystem::FileSystemController& fs_controller_,
|
||||
const ContentProvider& content_provider_)
|
||||
@@ -235,13 +243,10 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
if (found_best) {
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
if (active_update.is_external) {
|
||||
const auto* content_provider_union =
|
||||
static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto* content_provider_union = GetUnionProvider(content_provider);
|
||||
if (content_provider_union) {
|
||||
if (const auto* external_provider = content_provider_union->GetExternalProvider()) {
|
||||
best_update_raw = external_provider->GetEntryForVersion(
|
||||
update_tid, ContentRecordType::Program, best_version);
|
||||
}
|
||||
best_update_raw = content_provider_union->GetExternalEntryForVersion(
|
||||
update_tid, ContentRecordType::Program, best_version);
|
||||
}
|
||||
} else {
|
||||
best_update_raw = content_provider.GetEntryRaw(update_tid, ContentRecordType::Program);
|
||||
@@ -572,19 +577,25 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
if (found_best) {
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
if (active_update.is_external) {
|
||||
const auto* content_provider_union =
|
||||
static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto* content_provider_union = GetUnionProvider(content_provider);
|
||||
if (content_provider_union) {
|
||||
if (const auto* external_provider = content_provider_union->GetExternalProvider()) {
|
||||
best_update_raw = external_provider->GetEntryForVersion(
|
||||
update_tid, type, best_version);
|
||||
}
|
||||
best_update_raw =
|
||||
content_provider_union->GetExternalEntryForVersion(update_tid, type,
|
||||
best_version);
|
||||
}
|
||||
} else {
|
||||
best_update_raw = content_provider.GetEntryRaw(update_tid, type);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == ContentRecordType::Program || type == ContentRecordType::Control) {
|
||||
LOG_INFO(Loader,
|
||||
"PatchRomFS update lookup: title_id={:016X}, type={:02X}, found_best={}, "
|
||||
"best_version={}, best_update_raw={}",
|
||||
title_id, static_cast<u32>(type), found_best, best_version,
|
||||
best_update_raw != nullptr);
|
||||
}
|
||||
|
||||
// 3. Packed Update (Fallback)
|
||||
// If we still haven't found a best update, or if the packed one is enabled (named "Update"
|
||||
// usually? Or "Update (PACKED)"?) The GetPatches logic names it "Update" with version
|
||||
@@ -708,8 +719,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
|
||||
const auto* content_provider_union =
|
||||
static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto* content_provider_union = GetUnionProvider(content_provider);
|
||||
bool is_nand_control = true;
|
||||
bool is_nand_program = true;
|
||||
if (content_provider_union) {
|
||||
@@ -758,84 +768,79 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
|
||||
// Next, check for external updates
|
||||
if (content_provider_union) {
|
||||
const auto* external_provider = content_provider_union->GetExternalProvider();
|
||||
if (external_provider) {
|
||||
const auto updates = external_provider->ListUpdateVersions(update_tid);
|
||||
for (const auto& update : updates) {
|
||||
// Canonical name uses the raw version number (via FormatTitleVersion) so that
|
||||
// the disabled_addons key matches what PatchExeFS/PatchRomFS check at boot.
|
||||
// The NACP-derived string is only used as a UI display label.
|
||||
const auto canonical_version_str = FormatTitleVersion(update.version);
|
||||
const auto canonical_name =
|
||||
fmt::format("External Files/Update v{}", canonical_version_str);
|
||||
const auto updates = content_provider_union->ListExternalUpdateVersions(update_tid);
|
||||
for (const auto& update : updates) {
|
||||
// Canonical name uses the raw version number (via FormatTitleVersion) so that
|
||||
// the disabled_addons key matches what PatchExeFS/PatchRomFS check at boot.
|
||||
// The NACP-derived string is only used as a UI display label.
|
||||
const auto canonical_version_str = FormatTitleVersion(update.version);
|
||||
const auto canonical_name =
|
||||
fmt::format("External Files/Update v{}", canonical_version_str);
|
||||
|
||||
// Attempt to resolve a human-readable display version from the Control NCA.
|
||||
std::string display_version_str;
|
||||
const auto control_file = external_provider->GetEntryForVersion(
|
||||
update_tid, ContentRecordType::Control, update.version);
|
||||
if (control_file) {
|
||||
NCA control_nca(control_file);
|
||||
if (control_nca.GetStatus() == Loader::ResultStatus::Success) {
|
||||
if (auto control_romfs = control_nca.GetRomFS()) {
|
||||
if (auto extracted = ExtractRomFS(control_romfs)) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP control_nacp(nacp_file);
|
||||
display_version_str = CleanNacpVersion(control_nacp.GetVersionString());
|
||||
}
|
||||
// Attempt to resolve a human-readable display version from the Control NCA.
|
||||
std::string display_version_str;
|
||||
const auto control_file = content_provider_union->GetExternalEntryForVersion(
|
||||
update_tid, ContentRecordType::Control, update.version);
|
||||
if (control_file) {
|
||||
NCA control_nca(control_file);
|
||||
if (control_nca.GetStatus() == Loader::ResultStatus::Success) {
|
||||
if (auto control_romfs = control_nca.GetRomFS()) {
|
||||
if (auto extracted = ExtractRomFS(control_romfs)) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP control_nacp(nacp_file);
|
||||
display_version_str =
|
||||
CleanNacpVersion(control_nacp.GetVersionString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The name stored in disabled_addons is the canonical one (FormatTitleVersion),
|
||||
// ensuring stability across reboots regardless of NACP parse success.
|
||||
// Also check the NACP-derived name for backwards compatibility.
|
||||
bool patch_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), canonical_name) !=
|
||||
disabled.cend();
|
||||
if (!patch_disabled && !display_version_str.empty() &&
|
||||
display_version_str != canonical_version_str) {
|
||||
const auto nacp_name =
|
||||
fmt::format("External Files/Update v{}", display_version_str);
|
||||
patch_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), nacp_name) !=
|
||||
disabled.cend();
|
||||
}
|
||||
|
||||
// Use the human-readable string for display if available.
|
||||
const auto display_name = display_version_str.empty()
|
||||
? canonical_name
|
||||
: fmt::format("External Files/Update v{}",
|
||||
display_version_str);
|
||||
const auto version_str =
|
||||
display_version_str.empty() ? canonical_version_str : display_version_str;
|
||||
|
||||
// Deduplicate against installed system update if versions match
|
||||
bool exists = false;
|
||||
for (const auto& existing : out) {
|
||||
if (existing.type == PatchType::Update &&
|
||||
existing.version == canonical_version_str) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
// Store canonical_name as .name so the UI checkbox writes the right key to
|
||||
// disabled_addons, matching what PatchExeFS/PatchRomFS check.
|
||||
Patch update_patch = {.enabled = !patch_disabled,
|
||||
.name = canonical_name,
|
||||
.version = version_str,
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id};
|
||||
out.push_back(update_patch);
|
||||
}
|
||||
|
||||
// The name stored in disabled_addons is the canonical one (FormatTitleVersion),
|
||||
// ensuring stability across reboots regardless of NACP parse success.
|
||||
// Also check the NACP-derived name for backwards compatibility.
|
||||
bool patch_disabled = std::find(disabled.cbegin(), disabled.cend(), canonical_name) !=
|
||||
disabled.cend();
|
||||
if (!patch_disabled && !display_version_str.empty() &&
|
||||
display_version_str != canonical_version_str) {
|
||||
const auto nacp_name = fmt::format("External Files/Update v{}", display_version_str);
|
||||
patch_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), nacp_name) != disabled.cend();
|
||||
}
|
||||
|
||||
// Use the human-readable string for display if available.
|
||||
const auto display_name = display_version_str.empty()
|
||||
? canonical_name
|
||||
: fmt::format("External Files/Update v{}",
|
||||
display_version_str);
|
||||
const auto version_str =
|
||||
display_version_str.empty() ? canonical_version_str : display_version_str;
|
||||
|
||||
// Deduplicate against installed system update if versions match
|
||||
bool exists = false;
|
||||
for (const auto& existing : out) {
|
||||
if (existing.type == PatchType::Update &&
|
||||
existing.version == canonical_version_str) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
// Store canonical_name as .name so the UI checkbox writes the right key to
|
||||
// disabled_addons, matching what PatchExeFS/PatchRomFS check.
|
||||
Patch update_patch = {.enabled = !patch_disabled,
|
||||
.name = canonical_name,
|
||||
.version = version_str,
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id};
|
||||
out.push_back(update_patch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,53 +1093,48 @@ PatchManager::ActiveUpdate PatchManager::GetActiveUpdate() const {
|
||||
bool is_external = false;
|
||||
|
||||
// 1. External Updates
|
||||
const auto* content_provider_union =
|
||||
static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto* content_provider_union = GetUnionProvider(content_provider);
|
||||
if (content_provider_union) {
|
||||
const auto* external_provider = content_provider_union->GetExternalProvider();
|
||||
if (external_provider) {
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto updates = external_provider->ListUpdateVersions(update_tid);
|
||||
for (const auto& update : updates) {
|
||||
const auto canonical_version_str = FormatTitleVersion(update.version);
|
||||
const auto canonical_name =
|
||||
fmt::format("External Files/Update v{}", canonical_version_str);
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto updates = content_provider_union->ListExternalUpdateVersions(update_tid);
|
||||
for (const auto& update : updates) {
|
||||
const auto canonical_version_str = FormatTitleVersion(update.version);
|
||||
const auto canonical_name =
|
||||
fmt::format("External Files/Update v{}", canonical_version_str);
|
||||
|
||||
std::string nacp_version_str;
|
||||
const auto control_file = external_provider->GetEntryForVersion(
|
||||
update_tid, ContentRecordType::Control, update.version);
|
||||
if (control_file) {
|
||||
NCA control_nca(control_file);
|
||||
if (control_nca.GetStatus() == Loader::ResultStatus::Success) {
|
||||
if (auto control_romfs = control_nca.GetRomFS()) {
|
||||
if (auto extracted = ExtractRomFS(control_romfs)) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
nacp_version_str = CleanNacpVersion(nacp.GetVersionString());
|
||||
}
|
||||
std::string nacp_version_str;
|
||||
const auto control_file = content_provider_union->GetExternalEntryForVersion(
|
||||
update_tid, ContentRecordType::Control, update.version);
|
||||
if (control_file) {
|
||||
NCA control_nca(control_file);
|
||||
if (control_nca.GetStatus() == Loader::ResultStatus::Success) {
|
||||
if (auto control_romfs = control_nca.GetRomFS()) {
|
||||
if (auto extracted = ExtractRomFS(control_romfs)) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
nacp_version_str = CleanNacpVersion(nacp.GetVersionString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool patch_disabled = is_disabled(canonical_name);
|
||||
if (!patch_disabled && !nacp_version_str.empty() &&
|
||||
nacp_version_str != canonical_version_str) {
|
||||
const auto nacp_name =
|
||||
fmt::format("External Files/Update v{}", nacp_version_str);
|
||||
patch_disabled = is_disabled(nacp_name);
|
||||
}
|
||||
bool patch_disabled = is_disabled(canonical_name);
|
||||
if (!patch_disabled && !nacp_version_str.empty() &&
|
||||
nacp_version_str != canonical_version_str) {
|
||||
const auto nacp_name = fmt::format("External Files/Update v{}", nacp_version_str);
|
||||
patch_disabled = is_disabled(nacp_name);
|
||||
}
|
||||
|
||||
if (!patch_disabled) {
|
||||
if (!found_best || update.version > best_version) {
|
||||
best_version = update.version;
|
||||
found_best = true;
|
||||
is_external = true;
|
||||
}
|
||||
if (!patch_disabled) {
|
||||
if (!found_best || update.version > best_version) {
|
||||
best_version = update.version;
|
||||
found_best = true;
|
||||
is_external = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1232,31 +1232,38 @@ PatchManager::Metadata PatchManager::GetControlMetadata() const {
|
||||
}
|
||||
|
||||
const auto active_update = GetActiveUpdate();
|
||||
LOG_INFO(Loader,
|
||||
"GetControlMetadata: title_id={:016X}, active_update_found={}, version={}, "
|
||||
"is_external={}",
|
||||
title_id, active_update.found, active_update.version, active_update.is_external);
|
||||
if (active_update.found) {
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
if (active_update.is_external) {
|
||||
const auto* content_provider_union =
|
||||
static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto* content_provider_union = GetUnionProvider(content_provider);
|
||||
if (content_provider_union) {
|
||||
if (const auto* external_provider = content_provider_union->GetExternalProvider()) {
|
||||
auto control_file = external_provider->GetEntryForVersion(
|
||||
update_tid, ContentRecordType::Control, active_update.version);
|
||||
if (control_file) {
|
||||
control_nca = std::make_unique<NCA>(control_file);
|
||||
}
|
||||
auto control_file = content_provider_union->GetExternalEntryForVersion(
|
||||
update_tid, ContentRecordType::Control, active_update.version);
|
||||
if (control_file) {
|
||||
control_nca = std::make_unique<NCA>(control_file);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
control_nca = content_provider.GetEntry(update_tid, ContentRecordType::Control);
|
||||
}
|
||||
LOG_INFO(Loader, "GetControlMetadata: update control_nca={}", control_nca != nullptr);
|
||||
}
|
||||
|
||||
if (control_nca == nullptr) {
|
||||
control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
|
||||
LOG_INFO(Loader, "GetControlMetadata: base control_nca={}", control_nca != nullptr);
|
||||
}
|
||||
|
||||
if (control_nca == nullptr)
|
||||
if (control_nca == nullptr) {
|
||||
LOG_WARNING(Loader, "GetControlMetadata: no control NCA for title_id={:016X}", title_id);
|
||||
return {};
|
||||
}
|
||||
LOG_INFO(Loader, "GetControlMetadata: selected control status={}, type={:02X}",
|
||||
static_cast<u32>(control_nca->GetStatus()), static_cast<u32>(control_nca->GetType()));
|
||||
return ParseControlNCA(*control_nca);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <shared_mutex>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -71,7 +73,7 @@ static bool FollowsNcaIdFormat(std::string_view name) {
|
||||
static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
|
||||
bool within_two_digit, bool cnmt_suffix) {
|
||||
if (!within_two_digit) {
|
||||
const auto format_str = fmt::runtime(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca");
|
||||
const auto format_str = fmt::runtime(cnmt_suffix ? "/{}.cnmt.nca" : "/{}.nca");
|
||||
return fmt::format(format_str, Common::HexToString(nca_id, second_hex_upper));
|
||||
}
|
||||
|
||||
@@ -424,32 +426,87 @@ std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
|
||||
return ids;
|
||||
}
|
||||
|
||||
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
|
||||
void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids, std::map<u64, CNMT>& out_meta,
|
||||
std::map<u64, NcaID>& out_meta_id) const {
|
||||
for (const auto& id : ids) {
|
||||
const auto file = GetFileAtID(id);
|
||||
|
||||
if (file == nullptr)
|
||||
if (file == nullptr) {
|
||||
LOG_WARNING(Service_FS, "RegisteredCache ProcessFiles: missing file for nca_id={}",
|
||||
Common::HexToString(id, false));
|
||||
continue;
|
||||
}
|
||||
const auto nca = std::make_shared<NCA>(parser(file, id));
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success ||
|
||||
nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) {
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"RegisteredCache ProcessFiles: failed nca_id={}, status={}",
|
||||
Common::HexToString(id, false), static_cast<u16>(nca->GetStatus()));
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nca_type = nca->GetType();
|
||||
if (nca_type != NCAContentType::Meta) {
|
||||
LOG_INFO(Service_FS,
|
||||
"RegisteredCache ProcessFiles: skipped non-meta nca_id={}, title_id={:016X}, "
|
||||
"type={:02X}",
|
||||
Common::HexToString(id, false), nca->GetTitleId(), static_cast<u32>(nca_type));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca->GetSubdirectories().empty()) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"RegisteredCache ProcessFiles: meta has no sections nca_id={}, "
|
||||
"title_id={:016X}",
|
||||
Common::HexToString(id, false), nca->GetTitleId());
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
bool found_cnmt = false;
|
||||
|
||||
for (const auto& section0_file : section0->GetFiles()) {
|
||||
if (section0_file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
meta.insert_or_assign(nca->GetTitleId(), CNMT(section0_file));
|
||||
meta_id.insert_or_assign(nca->GetTitleId(), id);
|
||||
CNMT cnmt(section0_file);
|
||||
const auto title_id = cnmt.GetTitleID();
|
||||
LOG_INFO(Service_FS,
|
||||
"RegisteredCache ProcessFiles: parsed meta nca_id={}, nca_title_id={:016X}, "
|
||||
"cnmt_title_id={:016X}, title_type={:02X}, records={}",
|
||||
Common::HexToString(id, false), nca->GetTitleId(), title_id,
|
||||
static_cast<u32>(cnmt.GetType()), cnmt.GetContentRecords().size());
|
||||
|
||||
if (cnmt.GetType() == TitleType::AOC || cnmt.GetType() == TitleType::Update) {
|
||||
const auto& content_records = cnmt.GetContentRecords();
|
||||
for (std::size_t record_index = 0; record_index < content_records.size();
|
||||
++record_index) {
|
||||
const auto& record = content_records[record_index];
|
||||
LOG_INFO(Service_FS,
|
||||
"RegisteredCache ProcessFiles: meta record title_id={:016X}, "
|
||||
"record_index={}, record_type={:02X}, record_nca_id={}, "
|
||||
"record_file_found={}",
|
||||
title_id, record_index, static_cast<u32>(record.type),
|
||||
Common::HexToString(record.nca_id, false),
|
||||
GetFileAtID(record.nca_id) != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
out_meta.insert_or_assign(title_id, std::move(cnmt));
|
||||
out_meta_id.insert_or_assign(title_id, id);
|
||||
found_cnmt = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found_cnmt) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"RegisteredCache ProcessFiles: meta section lacks cnmt nca_id={}, "
|
||||
"title_id={:016X}",
|
||||
Common::HexToString(id, false), nca->GetTitleId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisteredCache::AccumulateCitronMeta() {
|
||||
void RegisteredCache::AccumulateCitronMeta(std::map<u64, CNMT>& out_citron_meta) const {
|
||||
const auto meta_dir = dir->GetSubdirectory("citron_meta");
|
||||
if (meta_dir == nullptr) {
|
||||
return;
|
||||
@@ -461,7 +518,7 @@ void RegisteredCache::AccumulateCitronMeta() {
|
||||
}
|
||||
|
||||
CNMT cnmt(file);
|
||||
citron_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
|
||||
out_citron_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,8 +528,20 @@ void RegisteredCache::Refresh() {
|
||||
}
|
||||
|
||||
const auto ids = AccumulateFiles();
|
||||
ProcessFiles(ids);
|
||||
AccumulateCitronMeta();
|
||||
std::map<u64, CNMT> new_meta;
|
||||
std::map<u64, NcaID> new_meta_id;
|
||||
std::map<u64, CNMT> new_citron_meta;
|
||||
|
||||
ProcessFiles(ids, new_meta, new_meta_id);
|
||||
AccumulateCitronMeta(new_citron_meta);
|
||||
|
||||
meta.swap(new_meta);
|
||||
meta_id.swap(new_meta_id);
|
||||
citron_meta.swap(new_citron_meta);
|
||||
|
||||
LOG_INFO(Service_FS,
|
||||
"RegisteredCache refreshed: path={}, nca_ids={}, meta_entries={}, citron_meta={}",
|
||||
dir->GetFullPath(), ids.size(), meta.size(), citron_meta.size());
|
||||
}
|
||||
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
|
||||
@@ -846,6 +915,7 @@ bool RegisteredCache::RawInstallCitronMeta(const CNMT& cnmt) {
|
||||
ContentProviderUnion::~ContentProviderUnion() = default;
|
||||
|
||||
const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
auto it = providers.find(ContentProviderUnionSlot::External);
|
||||
if (it != providers.end()) {
|
||||
return static_cast<const ExternalContentProvider*>(it->second);
|
||||
@@ -854,6 +924,7 @@ const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const
|
||||
}
|
||||
|
||||
const ContentProvider* ContentProviderUnion::GetSlotProvider(ContentProviderUnionSlot slot) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
auto it = providers.find(slot);
|
||||
if (it != providers.end()) {
|
||||
return it->second;
|
||||
@@ -862,14 +933,25 @@ const ContentProvider* ContentProviderUnion::GetSlotProvider(ContentProviderUnio
|
||||
}
|
||||
|
||||
void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
|
||||
std::unique_lock lock{providers_mutex};
|
||||
providers[slot] = provider;
|
||||
}
|
||||
|
||||
void ContentProviderUnion::SetSlots(
|
||||
std::initializer_list<std::pair<ContentProviderUnionSlot, ContentProvider*>> slots) {
|
||||
std::unique_lock lock{providers_mutex};
|
||||
for (const auto& [slot, provider] : slots) {
|
||||
providers[slot] = provider;
|
||||
}
|
||||
}
|
||||
|
||||
void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
|
||||
std::unique_lock lock{providers_mutex};
|
||||
providers[slot] = nullptr;
|
||||
}
|
||||
|
||||
void ContentProviderUnion::Refresh() {
|
||||
std::unique_lock lock{providers_mutex};
|
||||
for (auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -879,6 +961,7 @@ void ContentProviderUnion::Refresh() {
|
||||
}
|
||||
|
||||
bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -891,6 +974,7 @@ bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const
|
||||
}
|
||||
|
||||
std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -904,6 +988,7 @@ std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
|
||||
}
|
||||
|
||||
VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -917,6 +1002,7 @@ VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy
|
||||
}
|
||||
|
||||
VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -930,6 +1016,7 @@ VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType ty
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -947,6 +1034,7 @@ std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<ContentProviderEntry> out;
|
||||
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -967,6 +1055,7 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
|
||||
|
||||
std::shared_lock lock{providers_mutex};
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
@@ -988,6 +1077,7 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion
|
||||
|
||||
std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
|
||||
u64 title_id, ContentRecordType type) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
const auto iter =
|
||||
std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) {
|
||||
return provider.second != nullptr && provider.second->HasEntry(title_id, type);
|
||||
@@ -1000,6 +1090,29 @@ std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
|
||||
return iter->first;
|
||||
}
|
||||
|
||||
VirtualFile ContentProviderUnion::GetExternalEntryForVersion(u64 title_id, ContentRecordType type,
|
||||
u32 version) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
auto it = providers.find(ContentProviderUnionSlot::External);
|
||||
if (it == providers.end() || it->second == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<const ExternalContentProvider*>(it->second)
|
||||
->GetEntryForVersion(title_id, type, version);
|
||||
}
|
||||
|
||||
std::vector<ExternalUpdateEntry> ContentProviderUnion::ListExternalUpdateVersions(
|
||||
u64 title_id) const {
|
||||
std::shared_lock lock{providers_mutex};
|
||||
auto it = providers.find(ContentProviderUnionSlot::External);
|
||||
if (it == providers.end() || it->second == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return static_cast<const ExternalContentProvider*>(it->second)->ListUpdateVersions(title_id);
|
||||
}
|
||||
|
||||
ManualContentProvider::~ManualContentProvider() = default;
|
||||
|
||||
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
@@ -77,6 +80,8 @@ class ContentProvider {
|
||||
public:
|
||||
virtual ~ContentProvider();
|
||||
|
||||
virtual bool IsUnionProvider() const { return false; }
|
||||
|
||||
virtual void Refresh() = 0;
|
||||
|
||||
virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
@@ -195,8 +200,9 @@ private:
|
||||
std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
|
||||
std::vector<NcaID> AccumulateFiles() const;
|
||||
void ProcessFiles(const std::vector<NcaID>& ids);
|
||||
void AccumulateCitronMeta();
|
||||
void ProcessFiles(const std::vector<NcaID>& ids, std::map<u64, CNMT>& out_meta,
|
||||
std::map<u64, NcaID>& out_meta_id) const;
|
||||
void AccumulateCitronMeta(std::map<u64, CNMT>& out_citron_meta) const;
|
||||
std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetFileAtID(NcaID id) const;
|
||||
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& open_dir, std::string_view path) const;
|
||||
@@ -230,7 +236,11 @@ class ContentProviderUnion : public ContentProvider {
|
||||
public:
|
||||
~ContentProviderUnion() override;
|
||||
|
||||
bool IsUnionProvider() const override { return true; }
|
||||
|
||||
void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
|
||||
void SetSlots(
|
||||
std::initializer_list<std::pair<ContentProviderUnionSlot, ContentProvider*>> slots);
|
||||
void ClearSlot(ContentProviderUnionSlot slot);
|
||||
|
||||
void Refresh() override;
|
||||
@@ -251,10 +261,15 @@ public:
|
||||
std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id,
|
||||
ContentRecordType type) const;
|
||||
|
||||
VirtualFile GetExternalEntryForVersion(u64 title_id, ContentRecordType type,
|
||||
u32 version) const;
|
||||
std::vector<ExternalUpdateEntry> ListExternalUpdateVersions(u64 title_id) const;
|
||||
|
||||
const class ExternalContentProvider* GetExternalProvider() const;
|
||||
const ContentProvider* GetSlotProvider(ContentProviderUnionSlot slot) const;
|
||||
|
||||
private:
|
||||
mutable std::shared_mutex providers_mutex;
|
||||
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
|
||||
};
|
||||
|
||||
|
||||
@@ -195,6 +195,10 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
|
||||
LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName());
|
||||
continue;
|
||||
}
|
||||
if (!keys.PersistTicket(ticket)) {
|
||||
LOG_WARNING(Common_Filesystem, "Could not persist NSP ticket {}",
|
||||
ticket_file->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,22 @@ static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
|
||||
return add_on_content;
|
||||
}
|
||||
|
||||
static std::vector<u64> GetAOCTitleIDsForBase(const std::vector<u64>& add_on_content, u64 base) {
|
||||
std::vector<u64> out;
|
||||
for (const auto title_id : add_on_content) {
|
||||
if (CheckAOCTitleIDMatchesBase(title_id, base)) {
|
||||
out.push_back(title_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
static u32 GetGuestAOCIndex(std::size_t ordinal) {
|
||||
return static_cast<u32>(ordinal + 1);
|
||||
}
|
||||
|
||||
IAddOnContentManager::IAddOnContentManager(Core::System& system_)
|
||||
: ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)},
|
||||
service_context{system_, "aoc:u"} {
|
||||
@@ -98,8 +114,8 @@ IAddOnContentManager::~IAddOnContentManager() {
|
||||
}
|
||||
|
||||
Result IAddOnContentManager::CountAddOnContent(Out<u32> out_count, ClientProcessId process_id) {
|
||||
LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
|
||||
|
||||
// LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
|
||||
LOG_WARNING(Service_AOC, "CountAddOnContent called. process_id={}", process_id.pid);
|
||||
const auto current = system.GetApplicationProcessProgramID();
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[current];
|
||||
@@ -108,10 +124,10 @@ Result IAddOnContentManager::CountAddOnContent(Out<u32> out_count, ClientProcess
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
*out_count = static_cast<u32>(
|
||||
std::count_if(add_on_content.begin(), add_on_content.end(),
|
||||
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }));
|
||||
|
||||
*out_count = static_cast<u32>(GetAOCTitleIDsForBase(add_on_content, current).size());
|
||||
LOG_WARNING(Service_AOC,
|
||||
"CountAddOnContent: program_id={:016X}, count={}",
|
||||
current, *out_count);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -124,20 +140,16 @@ Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
|
||||
const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID());
|
||||
|
||||
std::vector<u32> out;
|
||||
const auto matching_aocs = GetAOCTitleIDsForBase(add_on_content, current);
|
||||
const auto& disabled = Settings::values.disabled_addons[current];
|
||||
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
|
||||
LOG_WARNING(Service_AOC, "Filtering AOCs for base title ID: {:016X}", current);
|
||||
for (u64 content_id : add_on_content) {
|
||||
const auto aoc_base = FileSys::GetBaseTitleID(content_id);
|
||||
if (aoc_base != current) {
|
||||
// LOG_WARNING(Service_AOC, "Skipping AOC {:016X} (Base: {:016X})", content_id,
|
||||
// aoc_base);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_AOC, "Match! AOC {:016X} belongs to current app. Adding to list.",
|
||||
content_id);
|
||||
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
|
||||
for (std::size_t i = 0; i < matching_aocs.size(); ++i) {
|
||||
const auto guest_index = GetGuestAOCIndex(i);
|
||||
LOG_WARNING(Service_AOC,
|
||||
"Match! AOC {:016X} belongs to current app. Adding guest_index={}.",
|
||||
matching_aocs[i], guest_index);
|
||||
out.push_back(guest_index);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_AOC, "DLCs are disabled for this title {:016X}", current);
|
||||
@@ -146,7 +158,23 @@ Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
|
||||
// TODO(DarkLordZach): Find the correct error code.
|
||||
R_UNLESS(out.size() >= offset, ResultUnknown);
|
||||
|
||||
*out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
|
||||
const auto buffer_count = out_addons.size() / sizeof(u32);
|
||||
*out_count = static_cast<u32>(std::min({out.size() - offset, static_cast<size_t>(count),
|
||||
buffer_count}));
|
||||
LOG_WARNING(Service_AOC,
|
||||
"ListAddOnContent result: base_id={:016X}, matched_total={}, offset={}, "
|
||||
"requested_count={}, returned_count={}, buffer_bytes={}",
|
||||
current, out.size(), offset, count, *out_count, out_addons.size());
|
||||
|
||||
for (u32 i = 0; i < *out_count; ++i) {
|
||||
const auto addon_index = out[offset + i];
|
||||
const auto physical_title_id = matching_aocs[addon_index - 1];
|
||||
|
||||
LOG_WARNING(Service_AOC,
|
||||
"ListAddOnContent output[{}]: addon_index={}, physical_title_id={:016X}",
|
||||
i, addon_index, physical_title_id);
|
||||
}
|
||||
|
||||
std::rotate(out.begin(), out.begin() + offset, out.end());
|
||||
|
||||
std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32));
|
||||
@@ -166,9 +194,7 @@ Result IAddOnContentManager::CountAddOnContentByApplicationId(Out<u32> out_count
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
*out_count = static_cast<u32>(
|
||||
std::count_if(add_on_content.begin(), add_on_content.end(),
|
||||
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }));
|
||||
*out_count = static_cast<u32>(GetAOCTitleIDsForBase(add_on_content, current).size());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -182,18 +208,16 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId(
|
||||
const auto current = FileSys::GetBaseTitleID(application_id);
|
||||
|
||||
std::vector<u32> out;
|
||||
const auto matching_aocs = GetAOCTitleIDsForBase(add_on_content, current);
|
||||
const auto& disabled = Settings::values.disabled_addons[current];
|
||||
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
|
||||
LOG_WARNING(Service_AOC, "Filtering AOCs for base title ID: {:016X}", current);
|
||||
for (u64 content_id : add_on_content) {
|
||||
const auto aoc_base = FileSys::GetBaseTitleID(content_id);
|
||||
if (aoc_base != current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_AOC, "Match! AOC {:016X} belongs to current app. Adding to list.",
|
||||
content_id);
|
||||
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
|
||||
for (std::size_t i = 0; i < matching_aocs.size(); ++i) {
|
||||
const auto guest_index = GetGuestAOCIndex(i);
|
||||
LOG_WARNING(Service_AOC,
|
||||
"Match! AOC {:016X} belongs to current app. Adding guest_index={}.",
|
||||
matching_aocs[i], guest_index);
|
||||
out.push_back(guest_index);
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_AOC, "DLCs are disabled for this title {:016X}", current);
|
||||
@@ -202,7 +226,24 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId(
|
||||
// TODO(DarkLordZach): Find the correct error code.
|
||||
R_UNLESS(out.size() >= offset, ResultUnknown);
|
||||
|
||||
*out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
|
||||
const auto buffer_count = out_addons.size() / sizeof(u32);
|
||||
*out_count = static_cast<u32>(std::min({out.size() - offset, static_cast<size_t>(count),
|
||||
buffer_count}));
|
||||
LOG_WARNING(Service_AOC,
|
||||
"ListAddOnContentByApplicationId result: base_id={:016X}, matched_total={}, "
|
||||
"offset={}, requested_count={}, returned_count={}, buffer_bytes={}",
|
||||
current, out.size(), offset, count, *out_count, out_addons.size());
|
||||
|
||||
for (u32 i = 0; i < *out_count; ++i) {
|
||||
const auto addon_index = out[offset + i];
|
||||
const auto physical_title_id = matching_aocs[addon_index - 1];
|
||||
|
||||
LOG_WARNING(Service_AOC,
|
||||
"ListAddOnContentByApplicationId output[{}]: addon_index={}, "
|
||||
"physical_title_id={:016X}",
|
||||
i, addon_index, physical_title_id);
|
||||
}
|
||||
|
||||
std::rotate(out.begin(), out.begin() + offset, out.end());
|
||||
|
||||
std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32));
|
||||
@@ -212,8 +253,8 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId(
|
||||
|
||||
Result IAddOnContentManager::GetAddOnContentBaseId(Out<u64> out_title_id,
|
||||
ClientProcessId process_id) {
|
||||
LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
|
||||
|
||||
// LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
|
||||
LOG_WARNING(Service_AOC, "GetAddOnContentBaseId called. process_id={}", process_id.pid);
|
||||
const auto title_id = system.GetApplicationProcessProgramID();
|
||||
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
@@ -221,16 +262,33 @@ Result IAddOnContentManager::GetAddOnContentBaseId(Out<u64> out_title_id,
|
||||
const auto res = pm.GetControlMetadata();
|
||||
if (res.first == nullptr) {
|
||||
*out_title_id = FileSys::GetAOCBaseTitleID(title_id);
|
||||
LOG_WARNING(Service_AOC,
|
||||
"GetAddOnContentBaseId fallback: program_id={:016X}, aoc_base={:016X}",
|
||||
title_id, *out_title_id);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
*out_title_id = res.first->GetDLCBaseTitleId();
|
||||
LOG_WARNING(Service_AOC,
|
||||
"GetAddOnContentBaseId metadata: program_id={:016X}, aoc_base={:016X}",
|
||||
title_id, *out_title_id);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAddOnContentManager::PrepareAddOnContent(s32 addon_index, ClientProcessId process_id) {
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called with addon_index={}, process_id={}", addon_index,
|
||||
const auto program_id = system.GetApplicationProcessProgramID();
|
||||
const auto aoc_base_id = FileSys::GetAOCBaseTitleID(program_id);
|
||||
const auto matching_aocs = GetAOCTitleIDsForBase(add_on_content, program_id);
|
||||
u64 physical_title_id = 0;
|
||||
if (addon_index > 0 && static_cast<std::size_t>(addon_index) <= matching_aocs.size()) {
|
||||
physical_title_id = matching_aocs[static_cast<std::size_t>(addon_index) - 1];
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_AOC,
|
||||
"PrepareAddOnContent: program_id={:016X}, aoc_base={:016X}, "
|
||||
"addon_index={}, physical_title_id={:016X}, process_id={}",
|
||||
program_id, aoc_base_id, addon_index, physical_title_id,
|
||||
process_id.pid);
|
||||
|
||||
R_SUCCEED();
|
||||
|
||||
@@ -803,45 +803,84 @@ FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const {
|
||||
return bis_factory->GetBCATDirectory(title_id);
|
||||
}
|
||||
|
||||
void FileSystemController::SetInitStage(InitStage stage) {
|
||||
init_stage = stage;
|
||||
}
|
||||
|
||||
InitStage FileSystemController::GetInitStage() const {
|
||||
return init_stage;
|
||||
}
|
||||
|
||||
void FileSystemController::InitializeContentSystem(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (init_stage < InitStage::FS_READY) {
|
||||
LOG_ERROR(Service_FS, "InitializeContentSystem called too early: init_stage={}",
|
||||
static_cast<u8>(init_stage));
|
||||
return;
|
||||
}
|
||||
|
||||
CreateFactories(vfs, overwrite);
|
||||
}
|
||||
|
||||
void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (overwrite) {
|
||||
bis_factory = nullptr;
|
||||
sdmc_factory = nullptr;
|
||||
external_provider = nullptr;
|
||||
if (init_stage < InitStage::FS_READY) {
|
||||
LOG_ERROR(Service_FS, "CreateFactories called too early: init_stage={}",
|
||||
static_cast<u8>(init_stage));
|
||||
return;
|
||||
}
|
||||
|
||||
using CitronPath = Common::FS::CitronPath;
|
||||
const auto sdmc_dir_path = Common::FS::GetCitronPath(CitronPath::SDMCDir);
|
||||
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
|
||||
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
||||
const auto nand_dir_path = Common::FS::GetCitronPathString(CitronPath::NANDDir);
|
||||
const auto load_dir_path = Common::FS::GetCitronPathString(CitronPath::LoadDir);
|
||||
const auto dump_dir_path = Common::FS::GetCitronPathString(CitronPath::DumpDir);
|
||||
|
||||
auto nand_directory =
|
||||
vfs.OpenDirectory(Common::FS::GetCitronPathString(CitronPath::NANDDir), rw_mode);
|
||||
LOG_INFO(Service_FS, "CreateFactories: overwrite={}, NANDDir={}, SDMCDir={}, LoadDir={}",
|
||||
overwrite, nand_dir_path, Common::FS::PathToUTF8String(sdmc_dir_path), load_dir_path);
|
||||
|
||||
auto nand_directory = vfs.OpenDirectory(nand_dir_path, rw_mode);
|
||||
auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode);
|
||||
auto load_directory = vfs.OpenDirectory(Common::FS::GetCitronPathString(CitronPath::LoadDir),
|
||||
FileSys::OpenMode::Read);
|
||||
auto load_directory = vfs.OpenDirectory(load_dir_path, FileSys::OpenMode::Read);
|
||||
auto sd_load_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path),
|
||||
FileSys::OpenMode::Read);
|
||||
auto dump_directory =
|
||||
vfs.OpenDirectory(Common::FS::GetCitronPathString(CitronPath::DumpDir), rw_mode);
|
||||
auto dump_directory = vfs.OpenDirectory(dump_dir_path, rw_mode);
|
||||
|
||||
if (bis_factory == nullptr) {
|
||||
bis_factory = std::make_unique<FileSys::BISFactory>(
|
||||
LOG_INFO(Service_FS,
|
||||
"CreateFactories: opened NAND={}, SDMC={}, Load={}, Dump={}",
|
||||
nand_directory ? nand_directory->GetFullPath() : "<null>",
|
||||
sd_directory ? sd_directory->GetFullPath() : "<null>",
|
||||
load_directory ? load_directory->GetFullPath() : "<null>",
|
||||
dump_directory ? dump_directory->GetFullPath() : "<null>");
|
||||
|
||||
std::unique_ptr<FileSys::BISFactory> new_bis_factory;
|
||||
auto* active_bis_factory = bis_factory.get();
|
||||
if (overwrite || active_bis_factory == nullptr) {
|
||||
new_bis_factory = std::make_unique<FileSys::BISFactory>(
|
||||
nand_directory, std::move(load_directory), std::move(dump_directory));
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,
|
||||
bis_factory->GetSystemNANDContents());
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND,
|
||||
bis_factory->GetUserNANDContents());
|
||||
active_bis_factory = new_bis_factory.get();
|
||||
}
|
||||
|
||||
if (sdmc_factory == nullptr) {
|
||||
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
|
||||
std::move(sd_load_directory));
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
|
||||
sdmc_factory->GetSDMCContents());
|
||||
if (active_bis_factory != nullptr) {
|
||||
const auto* sysnand = active_bis_factory->GetSystemNANDContents();
|
||||
const auto* usernand = active_bis_factory->GetUserNANDContents();
|
||||
LOG_INFO(Service_FS,
|
||||
"CreateFactories: prepared NAND providers, sys_entries={}, user_entries={}",
|
||||
sysnand ? sysnand->ListEntriesFilter().size() : 0,
|
||||
usernand ? usernand->ListEntriesFilter().size() : 0);
|
||||
}
|
||||
|
||||
if (external_provider == nullptr) {
|
||||
std::unique_ptr<FileSys::SDMCFactory> new_sdmc_factory;
|
||||
auto* active_sdmc_factory = sdmc_factory.get();
|
||||
if (overwrite || active_sdmc_factory == nullptr) {
|
||||
new_sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
|
||||
std::move(sd_load_directory));
|
||||
active_sdmc_factory = new_sdmc_factory.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<FileSys::ExternalContentProvider> new_external_provider;
|
||||
auto* active_external_provider = external_provider.get();
|
||||
if (overwrite || active_external_provider == nullptr) {
|
||||
std::vector<FileSys::VirtualDir> load_dirs;
|
||||
for (const auto& path : Settings::values.external_content_dirs) {
|
||||
auto dir = vfs.OpenDirectory(path, FileSys::OpenMode::Read);
|
||||
@@ -849,16 +888,36 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
load_dirs.push_back(std::move(dir));
|
||||
}
|
||||
}
|
||||
external_provider =
|
||||
new_external_provider =
|
||||
std::make_unique<FileSys::ExternalContentProvider>(std::move(load_dirs));
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External,
|
||||
external_provider.get());
|
||||
active_external_provider = new_external_provider.get();
|
||||
}
|
||||
|
||||
system.GetContentProviderUnion().SetSlots(
|
||||
{{FileSys::ContentProviderUnionSlot::SysNAND,
|
||||
active_bis_factory != nullptr ? active_bis_factory->GetSystemNANDContents() : nullptr},
|
||||
{FileSys::ContentProviderUnionSlot::UserNAND,
|
||||
active_bis_factory != nullptr ? active_bis_factory->GetUserNANDContents() : nullptr},
|
||||
{FileSys::ContentProviderUnionSlot::SDMC,
|
||||
active_sdmc_factory != nullptr ? active_sdmc_factory->GetSDMCContents() : nullptr},
|
||||
{FileSys::ContentProviderUnionSlot::External, active_external_provider}});
|
||||
|
||||
if (new_bis_factory != nullptr) {
|
||||
bis_factory = std::move(new_bis_factory);
|
||||
}
|
||||
if (new_sdmc_factory != nullptr) {
|
||||
sdmc_factory = std::move(new_sdmc_factory);
|
||||
}
|
||||
if (new_external_provider != nullptr) {
|
||||
external_provider = std::move(new_external_provider);
|
||||
}
|
||||
|
||||
// factory that handles sync tasks before a game is even selected
|
||||
if (global_save_data_factory == nullptr || overwrite) {
|
||||
global_save_data_factory = CreateSaveDataFactory(ProgramId{});
|
||||
}
|
||||
|
||||
init_stage = InitStage::CONTENT_READY;
|
||||
}
|
||||
|
||||
void FileSystemController::RefreshExternalContentProvider() {
|
||||
@@ -871,9 +930,11 @@ void FileSystemController::RefreshExternalContentProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
external_provider = std::make_unique<FileSys::ExternalContentProvider>(std::move(load_dirs));
|
||||
auto new_external_provider =
|
||||
std::make_unique<FileSys::ExternalContentProvider>(std::move(load_dirs));
|
||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External,
|
||||
external_provider.get());
|
||||
new_external_provider.get());
|
||||
external_provider = std::move(new_external_provider);
|
||||
}
|
||||
|
||||
void FileSystemController::Reset() {
|
||||
|
||||
@@ -68,6 +68,13 @@ enum class ImageDirectoryId : u32 {
|
||||
SdCard,
|
||||
};
|
||||
|
||||
enum class InitStage : u8 {
|
||||
NONE,
|
||||
SETTINGS_READY,
|
||||
FS_READY,
|
||||
CONTENT_READY,
|
||||
};
|
||||
|
||||
using ProcessId = u64;
|
||||
using ProgramId = u64;
|
||||
|
||||
@@ -130,9 +137,11 @@ public:
|
||||
|
||||
void RefreshExternalContentProvider();
|
||||
|
||||
// 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);
|
||||
// Single public entry for creating the SaveData, SDMC, BIS, and content providers.
|
||||
// Should be called once and before any function above is called.
|
||||
void InitializeContentSystem(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||
void SetInitStage(InitStage stage);
|
||||
InitStage GetInitStage() const;
|
||||
|
||||
// getter for main.cpp to trigger the sync between custom game paths for separate emulators
|
||||
FileSys::SaveDataFactory& GetSaveDataFactory() {
|
||||
@@ -143,6 +152,7 @@ public:
|
||||
|
||||
private:
|
||||
std::shared_ptr<FileSys::SaveDataFactory> CreateSaveDataFactory(ProgramId program_id);
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite);
|
||||
|
||||
struct Registration {
|
||||
ProgramId program_id;
|
||||
@@ -165,6 +175,12 @@ private:
|
||||
// Global factory for startup tasks and mirroring
|
||||
std::shared_ptr<FileSys::SaveDataFactory> global_save_data_factory;
|
||||
|
||||
#ifdef ANDROID
|
||||
InitStage init_stage{InitStage::NONE};
|
||||
#else
|
||||
InitStage init_stage{InitStage::FS_READY};
|
||||
#endif
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
@@ -16,12 +17,14 @@
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
@@ -49,6 +52,21 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
static std::vector<u64> GetAOCPhysicalTitleIDsForBase(const FileSys::ContentProvider& content_provider,
|
||||
u64 base) {
|
||||
std::vector<u64> out;
|
||||
const auto entries =
|
||||
content_provider.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
for (const auto& entry : entries) {
|
||||
if (FileSys::GetBaseTitleID(entry.title_id) == base) {
|
||||
out.push_back(entry.title_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
: ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()},
|
||||
content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} {
|
||||
@@ -490,10 +508,35 @@ Result FSP_SRV::OpenDataStorageByCurrentProcess(OutInterface<IStorage> out_inter
|
||||
|
||||
Result FSP_SRV::OpenDataStorageByDataId(OutInterface<IStorage> out_interface,
|
||||
FileSys::StorageId storage_id, u32 unknown, u64 title_id) {
|
||||
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
|
||||
storage_id, unknown, title_id);
|
||||
LOG_WARNING(Service_FS,
|
||||
"OpenDataStorageByDataId called with storage_id={:02X}, unknown={:08X}, "
|
||||
"title_id={:016X}",
|
||||
storage_id, unknown, title_id);
|
||||
|
||||
auto data = romfs_controller->OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||
u64 resolved_title_id = title_id;
|
||||
const auto requested_base = FileSys::GetBaseTitleID(title_id);
|
||||
const auto requested_aoc_base = FileSys::GetAOCBaseTitleID(title_id);
|
||||
const auto requested_aoc_id = FileSys::GetAOCID(title_id);
|
||||
const auto requested_is_aoc =
|
||||
requested_aoc_id > 0 && title_id >= requested_aoc_base &&
|
||||
title_id <= requested_aoc_base + FileSys::AOC_TITLE_ID_MASK;
|
||||
std::vector<u64> matching_aocs;
|
||||
|
||||
if (requested_is_aoc) {
|
||||
matching_aocs = GetAOCPhysicalTitleIDsForBase(content_provider, requested_base);
|
||||
if (requested_aoc_id <= matching_aocs.size()) {
|
||||
resolved_title_id = matching_aocs[static_cast<std::size_t>(requested_aoc_id) - 1];
|
||||
if (resolved_title_id != title_id) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"OpenDataStorageByDataId resolved AOC guest index: "
|
||||
"requested_title_id={:016X}, guest_index={}, physical_title_id={:016X}",
|
||||
title_id, requested_aoc_id, resolved_title_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto data =
|
||||
romfs_controller->OpenRomFS(resolved_title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||
|
||||
if (!data) {
|
||||
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
|
||||
@@ -503,16 +546,43 @@ Result FSP_SRV::OpenDataStorageByDataId(OutInterface<IStorage> out_interface,
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
if (requested_is_aoc) {
|
||||
std::size_t matching_aoc_count = 0;
|
||||
for (const auto entry_title_id : matching_aocs) {
|
||||
++matching_aoc_count;
|
||||
LOG_WARNING(Service_FS,
|
||||
"OpenDataStorageByDataId miss context: installed_aoc_index={}, "
|
||||
"installed_title_id={:016X}",
|
||||
matching_aoc_count, entry_title_id);
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_FS,
|
||||
"OpenDataStorageByDataId AOC miss: requested_base={:016X}, "
|
||||
"requested_aoc_base={:016X}, requested_aoc_index={}, "
|
||||
"resolved_title_id={:016X}, matching_installed_aoc_count={}",
|
||||
requested_base, requested_aoc_base, requested_aoc_id, resolved_title_id,
|
||||
matching_aoc_count);
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_FS,
|
||||
"Could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,
|
||||
storage_id);
|
||||
R_RETURN(FileSys::ResultTargetNotFound);
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm{title_id, fsc, content_provider};
|
||||
const auto opened_aoc_base = FileSys::GetAOCBaseTitleID(title_id);
|
||||
if (title_id >= opened_aoc_base && title_id <= opened_aoc_base + FileSys::AOC_TITLE_ID_MASK) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"OpenDataStorageByDataId opened AOC: base={:016X}, addon_index={}, "
|
||||
"requested_title_id={:016X}, physical_title_id={:016X}",
|
||||
FileSys::GetBaseTitleID(title_id), FileSys::GetAOCID(title_id), title_id,
|
||||
resolved_title_id);
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm{resolved_title_id, fsc, content_provider};
|
||||
|
||||
auto base =
|
||||
romfs_controller->OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||
romfs_controller->OpenBaseNca(resolved_title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||
auto storage = std::make_shared<IStorage>(
|
||||
system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data));
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
||||
std::stop_token token) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
|
||||
system.GetFileSystemController().InitializeContentSystem(*system.GetFilesystem(), false);
|
||||
|
||||
// clang-format off
|
||||
kernel.RunOnHostCoreProcess("audio", [&] { Audio::LoopProcess(system); }).detach();
|
||||
|
||||
Reference in New Issue
Block a user