sprofile: fully reimplement sprof:bg + sprof:sp
This commit is contained in:
@@ -21,6 +21,9 @@ namespace ams::sprofile::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char PrimaryDirectoryName[] = "primary";
|
||||
constexpr const char TemporaryDirectoryName[] = "temp";
|
||||
|
||||
Result CreateSaveData(const ProfileManager::SaveDataInfo &save_data_info) {
|
||||
R_TRY_CATCH(fs::CreateSystemSaveData(save_data_info.id, save_data_info.size, save_data_info.journal_size, save_data_info.flags)) {
|
||||
R_CATCH(fs::ResultPathAlreadyExists) { /* Nintendo accepts already-existing savedata here. */ }
|
||||
@@ -28,11 +31,70 @@ namespace ams::sprofile::srv {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void SafePrint(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
|
||||
|
||||
void SafePrint(char *dst, size_t dst_size, const char *fmt, ...) {
|
||||
std::va_list vl;
|
||||
va_start(vl, fmt);
|
||||
const size_t len = util::TVSNPrintf(dst, dst_size, fmt, vl);
|
||||
va_end(vl);
|
||||
|
||||
AMS_ABORT_UNLESS(len < dst_size);
|
||||
}
|
||||
|
||||
void CreateMetadataPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) {
|
||||
SafePrint(dst, dst_size, "%s:/%s/metadata", mount, dir);
|
||||
}
|
||||
|
||||
void CreatePrimaryMetadataPath(char *dst, size_t dst_size, const char *mount) {
|
||||
CreateMetadataPathImpl(dst, dst_size, mount, PrimaryDirectoryName);
|
||||
}
|
||||
|
||||
void CreateTemporaryMetadataPath(char *dst, size_t dst_size, const char *mount) {
|
||||
CreateMetadataPathImpl(dst, dst_size, mount, TemporaryDirectoryName);
|
||||
}
|
||||
|
||||
void CreateProfilePathImpl(char *dst, size_t dst_size, const char *mount, const char *dir, const Identifier &id) {
|
||||
SafePrint(dst, dst_size, "%s:/%s/profiles/%02x%02x%02x%02x%02x%02x%02x", mount, dir, id.data[0], id.data[1], id.data[2], id.data[3], id.data[4], id.data[5], id.data[6]);
|
||||
}
|
||||
|
||||
void CreatePrimaryProfilePath(char *dst, size_t dst_size, const char *mount, const Identifier &id) {
|
||||
CreateProfilePathImpl(dst, dst_size, mount, PrimaryDirectoryName, id);
|
||||
}
|
||||
|
||||
void CreateTemporaryProfilePath(char *dst, size_t dst_size, const char *mount, const Identifier &id) {
|
||||
CreateProfilePathImpl(dst, dst_size, mount, TemporaryDirectoryName, id);
|
||||
}
|
||||
|
||||
void CreateDirectoryPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) {
|
||||
SafePrint(dst, dst_size, "%s:/%s", mount, dir);
|
||||
}
|
||||
|
||||
void CreatePrimaryDirectoryPath(char *dst, size_t dst_size, const char *mount) {
|
||||
CreateDirectoryPathImpl(dst, dst_size, mount, PrimaryDirectoryName);
|
||||
}
|
||||
|
||||
void CreateTemporaryDirectoryPath(char *dst, size_t dst_size, const char *mount) {
|
||||
CreateDirectoryPathImpl(dst, dst_size, mount, TemporaryDirectoryName);
|
||||
}
|
||||
|
||||
void CreateProfileDirectoryPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) {
|
||||
SafePrint(dst, dst_size, "%s:/%s/profiles", mount, dir);
|
||||
}
|
||||
|
||||
void CreatePrimaryProfileDirectoryPath(char *dst, size_t dst_size, const char *mount) {
|
||||
CreateProfileDirectoryPathImpl(dst, dst_size, mount, PrimaryDirectoryName);
|
||||
}
|
||||
|
||||
void CreateTemporaryProfileDirectoryPath(char *dst, size_t dst_size, const char *mount) {
|
||||
CreateProfileDirectoryPathImpl(dst, dst_size, mount, TemporaryDirectoryName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ProfileManager::ProfileManager(const SaveDataInfo &save_data_info)
|
||||
: m_save_data_info(save_data_info), m_save_file_mounted(false),
|
||||
m_profile_metadata(util::nullopt), m_update_observer_manager()
|
||||
: m_save_data_info(save_data_info), m_save_file_mounted(false), m_profile_importer(util::nullopt),
|
||||
m_profile_metadata(util::nullopt), m_service_profile(util::nullopt), m_update_observer_manager()
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
@@ -45,9 +107,6 @@ namespace ams::sprofile::srv {
|
||||
/* Ensure the savedata exists. */
|
||||
if (R_SUCCEEDED(CreateSaveData(m_save_data_info))) {
|
||||
m_save_file_mounted = R_SUCCEEDED(fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id));
|
||||
|
||||
/* TODO: Remove this after implementation, as it's for debugging. */
|
||||
R_ABORT_UNLESS(fs::MountSdCard("sprof-dbg"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +126,7 @@ namespace ams::sprofile::srv {
|
||||
|
||||
/* Unload profile. */
|
||||
m_profile_metadata = util::nullopt;
|
||||
/* TODO m_service_profile = util::nullopt; */
|
||||
m_service_profile = util::nullopt;
|
||||
|
||||
/* Create the save data. */
|
||||
R_TRY(CreateSaveData(m_save_data_info));
|
||||
@@ -79,6 +138,124 @@ namespace ams::sprofile::srv {
|
||||
return result;
|
||||
}
|
||||
|
||||
Result ProfileManager::OpenProfileImporter() {
|
||||
/* Acquire locks. */
|
||||
std::scoped_lock lk1(m_profile_metadata_mutex);
|
||||
std::scoped_lock lk2(m_profile_importer_mutex);
|
||||
|
||||
/* Check that we don't already have an importer. */
|
||||
R_UNLESS(!m_profile_importer.has_value(), sprofile::ResultInvalidState());
|
||||
|
||||
/* Create importer. */
|
||||
m_profile_importer.emplace(m_profile_metadata);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::ImportProfile(const sprofile::srv::ProfileDataForImportData &data) {
|
||||
/* Acquire locks. */
|
||||
std::scoped_lock lk1(m_profile_importer_mutex);
|
||||
std::scoped_lock lk2(m_fs_mutex);
|
||||
|
||||
/* Check that we have an importer. */
|
||||
R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState());
|
||||
|
||||
/* Check that the metadata we're importing is valid. */
|
||||
R_UNLESS(data.data.version == ProfileDataVersion, sprofile::ResultInvalidDataVersion());
|
||||
|
||||
/* Succeed if we already have the profile. */
|
||||
R_SUCCEED_IF(m_profile_importer->HasProfile(data.identifier_0, data.identifier_1));
|
||||
|
||||
/* Check that we're importing the profile. */
|
||||
R_UNLESS(m_profile_importer->CanImportProfile(data.identifier_0), sprofile::ResultInvalidState());
|
||||
|
||||
/* Create temporary directories. */
|
||||
R_TRY(this->EnsureTemporaryDirectories());
|
||||
|
||||
/* Create profile. */
|
||||
char path[0x30];
|
||||
CreateTemporaryProfilePath(path, sizeof(path), m_save_data_info.mount_name, data.identifier_0);
|
||||
R_TRY(WriteFile(path, std::addressof(data.data), sizeof(data.data)));
|
||||
|
||||
/* Set profile imported. */
|
||||
m_profile_importer->OnImportProfile(data.identifier_0);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::Commit() {
|
||||
/* Acquire locks. */
|
||||
std::scoped_lock lk1(m_service_profile_mutex);
|
||||
std::scoped_lock lk2(m_profile_metadata_mutex);
|
||||
std::scoped_lock lk3(m_profile_importer_mutex);
|
||||
std::scoped_lock lk4(m_general_mutex);
|
||||
std::scoped_lock lk5(m_fs_mutex);
|
||||
|
||||
/* Check that we have an importer. */
|
||||
R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState());
|
||||
|
||||
/* Commit, and if we fail remount our save. */
|
||||
{
|
||||
/* Setup guard in case we fail. */
|
||||
auto remount_guard = SCOPE_GUARD {
|
||||
if (m_profile_importer.has_value()) {
|
||||
/* Unmount save file. */
|
||||
fs::Unmount(m_save_data_info.mount_name);
|
||||
m_save_file_mounted = false;
|
||||
|
||||
/* Re-mount save file. */
|
||||
R_ABORT_UNLESS(fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id));
|
||||
m_save_file_mounted = true;
|
||||
|
||||
/* Reset our importer. */
|
||||
m_profile_importer = util::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
/* Check that we can commit the importer. */
|
||||
R_UNLESS(m_profile_importer->CanCommit(), sprofile::ResultInvalidState());
|
||||
|
||||
/* Commit. */
|
||||
R_TRY(this->CommitImpl());
|
||||
|
||||
/* Commit the save file. */
|
||||
R_TRY(fs::CommitSaveData(m_save_data_info.mount_name));
|
||||
|
||||
/* We successfully committed. */
|
||||
remount_guard.Cancel();
|
||||
}
|
||||
|
||||
/* NOTE: Here nintendo generates an "sprofile_update_profile" sreport with the new and old revision keys. */
|
||||
|
||||
/* Handle tasks for when we've committed (including notifying update observers). */
|
||||
this->OnCommitted();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &data) {
|
||||
/* Acquire locks. */
|
||||
std::scoped_lock lk1(m_profile_importer_mutex);
|
||||
std::scoped_lock lk2(m_fs_mutex);
|
||||
|
||||
/* Check that we can import metadata. */
|
||||
R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState());
|
||||
R_UNLESS(m_profile_importer->CanImportMetadata(), sprofile::ResultInvalidState());
|
||||
|
||||
/* Check that the metadata we're importing is valid. */
|
||||
R_UNLESS(data.metadata.version == ProfileMetadataVersion, sprofile::ResultInvalidMetadataVersion());
|
||||
|
||||
/* Create temporary directories. */
|
||||
R_TRY(this->EnsureTemporaryDirectories());
|
||||
|
||||
/* Create metadata. */
|
||||
char path[0x30];
|
||||
CreateTemporaryMetadataPath(path, sizeof(path), m_save_data_info.mount_name);
|
||||
R_TRY(WriteFile(path, std::addressof(data.metadata), sizeof(data.metadata)));
|
||||
|
||||
/* Import the metadata. */
|
||||
m_profile_importer->ImportMetadata(data.metadata);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::LoadPrimaryMetadata(ProfileMetadata *out) {
|
||||
/* Acquire locks. */
|
||||
std::scoped_lock lk1(m_profile_metadata_mutex);
|
||||
@@ -92,7 +269,7 @@ namespace ams::sprofile::srv {
|
||||
|
||||
/* Read profile metadata. */
|
||||
char path[0x30];
|
||||
AMS_ABORT_UNLESS(util::TSNPrintf(path, sizeof(path), "%s:/%s/metadata", m_save_data_info.mount_name, "primary") < static_cast<int>(sizeof(path)));
|
||||
CreatePrimaryMetadataPath(path, sizeof(path), m_save_data_info.mount_name);
|
||||
R_TRY(ReadFile(path, std::addressof(*m_profile_metadata), sizeof(*m_profile_metadata), 0));
|
||||
|
||||
/* We read the metadata successfully. */
|
||||
@@ -104,4 +281,196 @@ namespace ams::sprofile::srv {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::LoadProfile(Identifier profile) {
|
||||
/* Check if we already have the profile. */
|
||||
if (m_service_profile.has_value()) {
|
||||
R_SUCCEED_IF(m_service_profile->name == profile);
|
||||
}
|
||||
|
||||
/* If we fail past this point, we want to have no profile. */
|
||||
auto prof_guard = SCOPE_GUARD { m_service_profile = util::nullopt; };
|
||||
|
||||
/* Create profile path. */
|
||||
char path[0x30];
|
||||
CreatePrimaryProfilePath(path, sizeof(path), m_save_data_info.mount_name, profile);
|
||||
|
||||
/* Load the profile. */
|
||||
m_service_profile = {};
|
||||
R_TRY(ReadFile(path, std::addressof(m_service_profile->data), sizeof(m_service_profile->data), 0));
|
||||
|
||||
/* We succeeded. */
|
||||
prof_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetDataEntry(ProfileDataEntry *out, Identifier profile, Identifier key) {
|
||||
/* Acquire locks. */
|
||||
std::scoped_lock lk1(m_service_profile_mutex);
|
||||
std::scoped_lock lk2(m_general_mutex);
|
||||
|
||||
/* Load the desired profile. */
|
||||
R_TRY(this->LoadProfile(profile));
|
||||
|
||||
/* Find the specified key. */
|
||||
for (auto i = 0u; i < m_service_profile->data.num_entries; ++i) {
|
||||
if (m_service_profile->data.entries[i].key == key) {
|
||||
*out = m_service_profile->data.entries[i];
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return sprofile::ResultKeyNotFound();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetSigned64(s64 *out, Identifier profile, Identifier key) {
|
||||
/* Get the data entry. */
|
||||
ProfileDataEntry entry;
|
||||
R_TRY(this->GetDataEntry(std::addressof(entry), profile, key));
|
||||
|
||||
/* Check the type. */
|
||||
R_UNLESS(entry.type == ValueType_S64, sprofile::ResultInvalidDataType());
|
||||
|
||||
/* Set the output value. */
|
||||
*out = entry.value_s64;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetUnsigned64(u64 *out, Identifier profile, Identifier key) {
|
||||
/* Get the data entry. */
|
||||
ProfileDataEntry entry;
|
||||
R_TRY(this->GetDataEntry(std::addressof(entry), profile, key));
|
||||
|
||||
/* Check the type. */
|
||||
R_UNLESS(entry.type == ValueType_U64, sprofile::ResultInvalidDataType());
|
||||
|
||||
/* Set the output value. */
|
||||
*out = entry.value_u64;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetSigned32(s32 *out, Identifier profile, Identifier key) {
|
||||
/* Get the data entry. */
|
||||
ProfileDataEntry entry;
|
||||
R_TRY(this->GetDataEntry(std::addressof(entry), profile, key));
|
||||
|
||||
/* Check the type. */
|
||||
R_UNLESS(entry.type == ValueType_S32, sprofile::ResultInvalidDataType());
|
||||
|
||||
/* Set the output value. */
|
||||
*out = entry.value_s32;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetUnsigned32(u32 *out, Identifier profile, Identifier key) {
|
||||
/* Get the data entry. */
|
||||
ProfileDataEntry entry;
|
||||
R_TRY(this->GetDataEntry(std::addressof(entry), profile, key));
|
||||
|
||||
/* Check the type. */
|
||||
R_UNLESS(entry.type == ValueType_U32, sprofile::ResultInvalidDataType());
|
||||
|
||||
/* Set the output value. */
|
||||
*out = entry.value_u32;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetByte(u8 *out, Identifier profile, Identifier key) {
|
||||
/* Get the data entry. */
|
||||
ProfileDataEntry entry;
|
||||
R_TRY(this->GetDataEntry(std::addressof(entry), profile, key));
|
||||
|
||||
/* Check the type. */
|
||||
R_UNLESS(entry.type == ValueType_Byte, sprofile::ResultInvalidDataType());
|
||||
|
||||
/* Set the output value. */
|
||||
*out = entry.value_u8;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::GetRaw(u8 *out_type, u64 *out_value, Identifier profile, Identifier key) {
|
||||
/* Get the data entry. */
|
||||
ProfileDataEntry entry;
|
||||
R_TRY(this->GetDataEntry(std::addressof(entry), profile, key));
|
||||
|
||||
/* Set the output type and value. */
|
||||
*out_type = entry.type;
|
||||
*out_value = entry.value_u64;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::CommitImpl() {
|
||||
/* Ensure primary directories. */
|
||||
R_TRY(this->EnsurePrimaryDirectories());
|
||||
|
||||
/* Declare re-usable paths. */
|
||||
char tmp_path[0x30];
|
||||
char pri_path[0x30];
|
||||
|
||||
/* Move the metadata. */
|
||||
{
|
||||
CreateTemporaryMetadataPath(tmp_path, sizeof(tmp_path), m_save_data_info.mount_name);
|
||||
CreatePrimaryMetadataPath(pri_path, sizeof(pri_path), m_save_data_info.mount_name);
|
||||
R_TRY(MoveFile(tmp_path, pri_path));
|
||||
}
|
||||
|
||||
/* Move all profiles. */
|
||||
for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) {
|
||||
const auto id = m_profile_importer->GetImportingProfile(i);
|
||||
|
||||
CreateTemporaryProfilePath(tmp_path, sizeof(tmp_path), m_save_data_info.mount_name, id);
|
||||
CreatePrimaryProfilePath(pri_path, sizeof(pri_path), m_save_data_info.mount_name, id);
|
||||
R_TRY(MoveFile(tmp_path, pri_path));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void ProfileManager::OnCommitted() {
|
||||
/* If we need to, invalidate the loaded service profile. */
|
||||
if (m_service_profile.has_value()) {
|
||||
for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) {
|
||||
if (m_service_profile->name == m_profile_importer->GetImportingProfile(i)) {
|
||||
m_service_profile = util::nullopt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Here, Nintendo sets the erpt ServiceProfileRevisionKey to the current revision key. */
|
||||
|
||||
/* Reset profile metadata. */
|
||||
m_profile_metadata = util::nullopt;
|
||||
|
||||
/* Invoke any listeners. */
|
||||
for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) {
|
||||
m_update_observer_manager.OnUpdate(m_profile_importer->GetImportingProfile(i));
|
||||
}
|
||||
}
|
||||
|
||||
Result ProfileManager::EnsurePrimaryDirectories() {
|
||||
/* Ensure the primary directories. */
|
||||
char path[0x30];
|
||||
|
||||
CreatePrimaryDirectoryPath(path, sizeof(path), m_save_data_info.mount_name);
|
||||
R_TRY(EnsureDirectory(path));
|
||||
|
||||
CreatePrimaryProfileDirectoryPath(path, sizeof(path), m_save_data_info.mount_name);
|
||||
R_TRY(EnsureDirectory(path));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ProfileManager::EnsureTemporaryDirectories() {
|
||||
/* Ensure the temporary directories. */
|
||||
char path[0x30];
|
||||
|
||||
CreateTemporaryDirectoryPath(path, sizeof(path), m_save_data_info.mount_name);
|
||||
R_TRY(EnsureDirectory(path));
|
||||
|
||||
CreateTemporaryProfileDirectoryPath(path, sizeof(path), m_save_data_info.mount_name);
|
||||
R_TRY(EnsureDirectory(path));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user