// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <span>
#include <thread>
#include <vector>

#include <magic_enum/magic_enum.hpp>

#include "common/assert.h"
#include "common/config.h"
#include "common/cstring.h"
#include "common/elf_info.h"
#include "common/enum.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/string_util.h"
#include "core/file_format/psf.h"
#include "core/file_sys/fs.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/save_data/savedata.h"
#include "core/libraries/save_data/savedata_error.h"
#include "core/libraries/system/msgdialog.h"
#include "core/libraries/system/msgdialog_ui.h"
#include "save_backup.h"
#include "save_instance.h"
#include "save_memory.h"

namespace fs = std::filesystem;
namespace chrono = std::chrono;

using Common::CString;
using Common::ElfInfo;

namespace Libraries::SaveData {

enum class OrbisSaveDataSaveDataMemoryOption : u32 {
    NONE = 0,
    SET_PARAM = 1 << 0,
    DOUBLE_BUFFER = 1 << 1,
    UNLOCK_LIMITATIONS = 1 << 2,
};

using OrbisUserServiceUserId = s32;
using OrbisSaveDataBlocks = u64;

constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
constexpr u32 OrbisSaveDataBlocksMin2 = 96;   // 3MiB
constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB

// Maximum size for a mount point "/savedataN", where N is a number
constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16;

constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size

enum class OrbisSaveDataMountMode : u32 {
    RDONLY = 1 << 0,
    RDWR = 1 << 1,
    CREATE = 1 << 2,
    DESTRUCT_OFF = 1 << 3,
    COPY_ICON = 1 << 4,
    CREATE2 = 1 << 5,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisSaveDataMountMode);

enum class OrbisSaveDataMountStatus : u32 {
    NOTHING = 0,
    CREATED = 1,
};

enum class OrbisSaveDataParamType : u32 {
    ALL = 0,
    TITLE = 1,
    SUB_TITLE = 2,
    DETAIL = 3,
    USER_PARAM = 4,
    MTIME = 5,
};

enum class OrbisSaveDataSortKey : u32 {
    DIRNAME = 0,
    USER_PARAM = 1,
    BLOCKS = 2,
    MTIME = 3,
    FREE_BLOCKS = 5,
};

enum class OrbisSaveDataSortOrder : u32 {
    ASCENT = 0,
    DESCENT = 1,
};

struct OrbisSaveDataFingerprint {
    CString<OrbisSaveDataFingerprintDataSize> data;
    std::array<u8, 15> _pad;
};

struct OrbisSaveDataBackup {
    OrbisUserServiceUserId userId;
    s32 : 32;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    const OrbisSaveDataFingerprint* param;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataCheckBackupData {
    OrbisUserServiceUserId userId;
    s32 : 32;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    OrbisSaveDataParam* param;
    OrbisSaveDataIcon* icon;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataDelete {
    OrbisUserServiceUserId userId;
    s32 : 32;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    u32 _unused;
    std::array<u8, 32> _reserved;
    s32 : 32;
};

struct OrbisSaveDataIcon {
    void* buf;
    size_t bufSize;
    size_t dataSize;
    std::array<u8, 32> _reserved;

    Error LoadIcon(const fs::path& icon_path) {
        try {
            const Common::FS::IOFile file(icon_path, Common::FS::FileAccessMode::Read);
            dataSize = file.GetSize();
            file.Seek(0);
            file.ReadRaw<u8>(buf, std::min(bufSize, dataSize));
        } catch (const fs::filesystem_error& e) {
            LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());
            return Error::INTERNAL;
        }
        return Error::OK;
    }
};

struct OrbisSaveDataMemoryData {
    void* buf;
    size_t bufSize;
    s64 offset;
    u8 _reserved[40];
};

struct OrbisSaveDataMemoryGet2 {
    OrbisUserServiceUserId userId;
    std::array<u8, 4> _pad;
    OrbisSaveDataMemoryData* data;
    OrbisSaveDataParam* param;
    OrbisSaveDataIcon* icon;
    u32 slotId;
    std::array<u8, 28> _reserved;
};

struct OrbisSaveDataMemorySet2 {
    OrbisUserServiceUserId userId;
    std::array<u8, 4> _pad;
    const OrbisSaveDataMemoryData* data;
    const OrbisSaveDataParam* param;
    const OrbisSaveDataIcon* icon;
    u32 dataNum;
    u32 slotId;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataMemorySetup2 {
    OrbisSaveDataSaveDataMemoryOption option;
    OrbisUserServiceUserId userId;
    size_t memorySize;
    size_t iconMemorySize;
    // +4.5
    const OrbisSaveDataParam* initParam;
    // +4.5
    const OrbisSaveDataIcon* initIcon;
    u32 slotId;
    std::array<u8, 20> _reserved;
};

struct OrbisSaveDataMemorySetupResult {
    size_t existedMemorySize;
    std::array<u8, 16> _reserved;
};

enum OrbisSaveDataMemorySyncOption : u32 {
    NONE = 0,
    BLOCKING = 1,
};

struct OrbisSaveDataMemorySync {
    OrbisUserServiceUserId userId;
    u32 slotId;
    OrbisSaveDataMemorySyncOption option;
    std::array<u8, 28> _reserved;
};

struct OrbisSaveDataMount2 {
    OrbisUserServiceUserId userId;
    s32 : 32;
    const OrbisSaveDataDirName* dirName;
    OrbisSaveDataBlocks blocks;
    OrbisSaveDataMountMode mountMode;
    std::array<u8, 32> _reserved;
    s32 : 32;
};

struct OrbisSaveDataMount {
    OrbisUserServiceUserId userId;
    s32 : 32;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    const OrbisSaveDataFingerprint* fingerprint;
    OrbisSaveDataBlocks blocks;
    OrbisSaveDataMountMode mountMode;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataMountInfo {
    OrbisSaveDataBlocks blocks;
    OrbisSaveDataBlocks freeBlocks;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataMountPoint {
    CString<OrbisSaveDataMountPointDataMaxsize> data;
};

struct OrbisSaveDataMountResult {
    OrbisSaveDataMountPoint mount_point;
    OrbisSaveDataBlocks required_blocks;
    u32 _unused;
    // +4.5
    OrbisSaveDataMountStatus mount_status;
    std::array<u8, 28> _reserved;
    s32 : 32;
};

struct OrbisSaveDataRestoreBackupData {
    OrbisUserServiceUserId userId;
    s32 : 32;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    const OrbisSaveDataFingerprint* fingerprint;
    u32 _unused;
    std::array<u8, 32> _reserved;
    s32 : 32;
};

struct OrbisSaveDataTransferringMount {
    OrbisUserServiceUserId userId;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    const OrbisSaveDataFingerprint* fingerprint;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataDirNameSearchCond {
    OrbisUserServiceUserId userId;
    int : 32;
    const OrbisSaveDataTitleId* titleId;
    const OrbisSaveDataDirName* dirName;
    OrbisSaveDataSortKey key;
    OrbisSaveDataSortOrder order;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataSearchInfo {
    u64 blocks;
    u64 freeBlocks;
    std::array<u8, 32> _reserved;
};

struct OrbisSaveDataDirNameSearchResult {
    u32 hitNum;
    int : 32;
    OrbisSaveDataDirName* dirNames;
    u32 dirNamesNum;
    // +1.7
    u32 setNum;
    // +1.7
    OrbisSaveDataParam* params;
    // +2.5
    OrbisSaveDataSearchInfo* infos;
    std::array<u8, 12> _reserved;
    int : 32;
};

struct OrbisSaveDataEventParam { // dummy structure
    OrbisSaveDataEventParam() = delete;
};

using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType;

struct OrbisSaveDataEvent {
    OrbisSaveDataEventType type;
    s32 errorCode;
    OrbisUserServiceUserId userId;
    std::array<u8, 4> _pad;
    OrbisSaveDataTitleId titleId;
    OrbisSaveDataDirName dirName;
    std::array<u8, 40> _reserved;
};

static bool g_initialized = false;
static std::string g_game_serial;
static u32 g_fw_ver;
static std::array<std::optional<SaveInstance>, 16> g_mount_slots;

static void initialize() {
    g_initialized = true;
    g_game_serial = Common::Singleton<PSF>::Instance()
                        ->GetString("INSTALL_DIR_SAVEDATA")
                        .value_or(ElfInfo::Instance().GameSerial());
    g_fw_ver = ElfInfo::Instance().FirmwareVer();
    Backup::StartThread();
}

// game_00other | game*other

static bool match(std::string_view str, std::string_view pattern) {
    auto str_it = str.begin();
    auto pat_it = pattern.begin();
    while (str_it != str.end() && pat_it != pattern.end()) {
        if (*pat_it == '%') { // 0 or more wildcard
            for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) {
                if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) {
                    return true;
                }
            }
            return false;
        }
        if (*pat_it == '_') { // 1 character wildcard
            ++str_it;
            ++pat_it;
            continue;
        }
        if (*pat_it != *str_it) {
            return false;
        }
        ++str_it;
        ++pat_it;
    }
    return str_it == str.end() && pat_it == pattern.end();
}

static Error setNotInitializedError() {
    if (g_fw_ver < ElfInfo::FW_20) {
        return Error::INTERNAL;
    }
    if (g_fw_ver < ElfInfo::FW_25) {
        return Error::USER_SERVICE_NOT_INITIALIZED;
    }
    return Error::NOT_INITIALIZED;
}

static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
                           OrbisSaveDataMountResult* mount_result,
                           std::string_view title_id = g_game_serial) {

    if (mount_info->userId < 0) {
        return Error::INVALID_LOGIN_USER;
    }
    if (mount_info->dirName == nullptr) {
        LOG_INFO(Lib_SaveData, "called without dirName");
        return Error::PARAMETER;
    }

    // check backup status
    {
        const auto save_path =
            SaveInstance::MakeDirSavePath(mount_info->userId, title_id, mount_info->dirName->data);
        if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) {
            return Error::BACKUP_BUSY;
        }
    }

    auto mount_mode = mount_info->mountMode;
    const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY);

    const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE);
    const bool create_if_not_exist =
        True(mount_mode & OrbisSaveDataMountMode::CREATE2) && g_fw_ver >= ElfInfo::FW_45;
    ASSERT(!create || !create_if_not_exist); // Can't have both

    const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON);

    const bool ignore_corrupt =
        True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF) || g_fw_ver < ElfInfo::FW_16;

    const std::string_view dir_name{mount_info->dirName->data};

    // find available mount point
    int slot_num = -1;
    for (size_t i = 0; i < g_mount_slots.size(); i++) {
        const auto& instance = g_mount_slots[i];
        if (instance.has_value()) {
            const auto& slot_name = instance->GetDirName();
            if (slot_name == dir_name) {
                return Error::BUSY;
            }
        } else {
            slot_num = static_cast<int>(i);
            break;
        }
    }
    if (slot_num == -1) {
        return Error::MOUNT_FULL;
    }

    SaveInstance save_instance{slot_num, mount_info->userId, std::string{title_id}, dir_name,
                               (int)mount_info->blocks};

    if (save_instance.Mounted()) {
        UNREACHABLE_MSG("Save instance should not be mounted");
    }

    if (!create && !create_if_not_exist && !save_instance.Exists()) {
        return Error::NOT_FOUND;
    }
    if (create && save_instance.Exists()) {
        return Error::EXISTS;
    }

    bool to_be_created = !save_instance.Exists();

    if (to_be_created) { // Check size

        if (mount_info->blocks < OrbisSaveDataBlocksMin2 ||
            mount_info->blocks > OrbisSaveDataBlocksMax) {
            LOG_INFO(Lib_SaveData, "called with invalid block size");
        }

        const auto root_save = Config::GetSaveDataPath();
        fs::create_directories(root_save);
        const auto available = fs::space(root_save).available;

        auto requested_size = save_instance.GetMaxBlocks() * OrbisSaveDataBlockSize;
        if (requested_size > available) {
            mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize;
            return Error::NO_SPACE_FS;
        }
    }

    try {
        save_instance.SetupAndMount(is_ro, copy_icon, ignore_corrupt);
    } catch (const fs::filesystem_error& e) {
        if (e.code() == std::errc::illegal_byte_sequence) {
            LOG_ERROR(Lib_SaveData, "Corrupted save data");
            return Error::BROKEN;
        }
        if (e.code() == std::errc::no_space_on_device) {
            return Error::NO_SPACE_FS;
        }
        LOG_ERROR(Lib_SaveData, "Failed to mount save data: {}", e.what());
        return Error::INTERNAL;
    }

    mount_result->mount_point.data.FromString(save_instance.GetMountPoint());

    if (g_fw_ver >= ElfInfo::FW_45) {
        mount_result->mount_status = create_if_not_exist && to_be_created
                                         ? OrbisSaveDataMountStatus::CREATED
                                         : OrbisSaveDataMountStatus::NOTHING;
    }

    g_mount_slots[slot_num].emplace(std::move(save_instance));

    return Error::OK;
}

static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mountPoint == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view());
    const std::string_view mount_point_str{mountPoint->data};
    for (auto& instance : g_mount_slots) {
        if (instance.has_value()) {
            const auto& slot_name = instance->GetMountPoint();
            if (slot_name == mount_point_str) {
                // TODO: check if is busy
                instance->Umount();
                if (call_backup) {
                    Backup::StartThread();
                    Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(),
                                       instance->GetDirName(),
                                       OrbisSaveDataEventType::UMOUNT_BACKUP);
                }
                instance.reset();
                return Error::OK;
            }
        }
    }
    return Error::NOT_FOUND;
}

void OrbisSaveDataParam::FromSFO(const PSF& sfo) {
    memset(this, 0, sizeof(OrbisSaveDataParam));
    title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown"));
    subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or(""));
    detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or(""));
    userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
    const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
    mtime = chrono::duration_cast<chrono::seconds>(time_since_epoch).count();
}

void OrbisSaveDataParam::ToSFO(PSF& sfo) const {
    sfo.AddString(std::string{SaveParams::MAINTITLE}, std::string{title}, true);
    sfo.AddString(std::string{SaveParams::SUBTITLE}, std::string{subTitle}, true);
    sfo.AddString(std::string{SaveParams::DETAIL}, std::string{detail}, true);
    sfo.AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, static_cast<s32>(userParam), true);
}

int PS4_SYSV_ABI sceSaveDataAbort() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (backup == nullptr || backup->dirName == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }

    const std::string_view dir_name{backup->dirName->data};
    LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name);

    std::string_view title{backup->titleId != nullptr ? std::string_view{backup->titleId->data}
                                                      : std::string_view{g_game_serial}};

    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetUserId() == backup->userId &&
            instance->GetTitleId() == title && instance->GetDirName() == dir_name) {
            return Error::BUSY;
        }
    }

    Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP);

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataBindPsnAccount() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataChangeDatabase() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataChangeInternal() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (check == nullptr || check->dirName == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }

    const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data}
                                                           : std::string_view{g_game_serial}};
    LOG_DEBUG(Lib_SaveData, "called with titleId={}", title);

    const auto save_path =
        SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data);

    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetSavePath() == save_path) {
            return Error::BUSY;
        }
    }

    if (Backup::IsBackupExecutingFor(save_path)) {
        return Error::BACKUP_BUSY;
    }

    const auto backup_path = Backup::MakeBackupPath(save_path);
    if (!fs::exists(backup_path)) {
        return Error::NOT_FOUND;
    }

    if (check->param != nullptr) {
        PSF sfo;
        if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) {
            LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", fmt::UTF(backup_path.u8string()));
            return Error::INTERNAL;
        }
        check->param->FromSFO(sfo);
    }

    if (check->icon != nullptr) {
        const auto icon_path = backup_path / "sce_sys" / "icon0.png";
        if (fs::exists(icon_path) && check->icon->LoadIcon(icon_path) != Error::OK) {
            return Error::INTERNAL;
        }
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCheckCloudData() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataClearProgress() {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    LOG_DEBUG(Lib_SaveData, "called");
    Backup::ClearProgress();
    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataCopy5() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataCreateUploadData() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebug() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebugCleanMount() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebugCompiledSdkVersion() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebugGetThreadId() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDebugTarget() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (del == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    const std::string_view dirName{del->dirName->data};
    LOG_DEBUG(Lib_SaveData, "called dirName: {}", dirName);
    if (dirName.empty()) {
        return Error::PARAMETER;
    }
    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetDirName() == dirName) {
            return Error::BUSY;
        }
    }
    const auto save_path = SaveInstance::MakeDirSavePath(del->userId, g_game_serial, dirName);
    try {
        if (fs::exists(save_path)) {
            fs::remove_all(save_path);
        }
    } catch (const fs::filesystem_error& e) {
        LOG_ERROR(Lib_SaveData, "Failed to delete save data: {}", e.what());
        return Error::INTERNAL;
    }
    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataDelete5() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDeleteAllUser() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDeleteCloudData() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDeleteUser() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond,
                                            OrbisSaveDataDirNameSearchResult* result) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS ||
        cond->order > OrbisSaveDataSortOrder::DESCENT) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called");
    const std::string_view title_id{cond->titleId == nullptr
                                        ? std::string_view{g_game_serial}
                                        : std::string_view{cond->titleId->data}};
    const auto save_path = SaveInstance::MakeTitleSavePath(cond->userId, title_id);

    if (!fs::exists(save_path)) {
        result->hitNum = 0;
        if (g_fw_ver >= ElfInfo::FW_17) {
            result->setNum = 0;
        }
        return Error::OK;
    }

    std::vector<std::string> dir_list;

    for (const auto& path : fs::directory_iterator{save_path}) {
        auto dir_name = path.path().filename().string();
        // skip non-directories, sce_* and directories without param.sfo
        if (fs::is_directory(path) && !dir_name.starts_with("sce_") &&
            fs::exists(SaveInstance::GetParamSFOPath(path))) {
            dir_list.push_back(dir_name);
        }
    }
    if (cond->dirName != nullptr) {
        // Filter names
        const auto pat = Common::ToLower(std::string_view{cond->dirName->data});
        if (!pat.empty()) {
            std::erase_if(dir_list, [&](const std::string& dir_name) {
                return !match(Common::ToLower(dir_name), pat);
            });
        }
    }

    std::unordered_map<std::string, PSF> map_dir_sfo;
    std::unordered_map<std::string, int> map_max_blocks;
    std::unordered_map<std::string, OrbisSaveDataBlocks> map_free_size;

    for (const auto& dir_name : dir_list) {
        const auto dir_path = SaveInstance::MakeDirSavePath(cond->userId, title_id, dir_name);
        const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path);
        PSF sfo;
        if (!sfo.Open(sfo_path)) {
            LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string()));
            ASSERT_MSG(false, "Failed to read SFO");
        }

        size_t size = Common::FS::GetDirectorySize(dir_path);
        size_t total = SaveInstance::GetMaxBlockFromSFO(sfo);

        map_dir_sfo.emplace(dir_name, std::move(sfo));
        map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize);
        map_max_blocks.emplace(dir_name, total);
    }

#define PROJ(x) [&](const auto& v) { return x; }
    switch (cond->key) {
    case OrbisSaveDataSortKey::DIRNAME:
        std::ranges::stable_sort(dir_list);
        break;
    case OrbisSaveDataSortKey::USER_PARAM:
        std::ranges::stable_sort(
            dir_list, {},
            PROJ(map_dir_sfo.at(v).GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0)));
        break;
    case OrbisSaveDataSortKey::BLOCKS:
        std::ranges::stable_sort(dir_list, {}, PROJ(map_max_blocks.at(v)));
        break;
    case OrbisSaveDataSortKey::FREE_BLOCKS:
        std::ranges::stable_sort(dir_list, {}, PROJ(map_free_size.at(v)));
        break;
    case OrbisSaveDataSortKey::MTIME:
        std::ranges::stable_sort(dir_list, {}, PROJ(map_dir_sfo.at(v).GetLastWrite()));
        break;
    }
#undef PROJ

    if (cond->order == OrbisSaveDataSortOrder::DESCENT) {
        std::ranges::reverse(dir_list);
    }

    size_t max_count = std::min(static_cast<size_t>(result->dirNamesNum), dir_list.size());
    if (g_fw_ver >= ElfInfo::FW_17) {
        result->hitNum = dir_list.size();
        result->setNum = max_count;
    } else {
        result->hitNum = max_count;
    }

    for (size_t i = 0; i < max_count; i++) {
        auto& name_data = result->dirNames[i].data;
        name_data.FromString(dir_list[i]);

        if (g_fw_ver >= ElfInfo::FW_17 && result->params != nullptr) {
            auto& sfo = map_dir_sfo.at(dir_list[i]);
            auto& param_data = result->params[i];
            param_data.FromSFO(sfo);
        }

        if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) {
            auto& info = result->infos[i];
            info.blocks = map_max_blocks.at(dir_list[i]);
            info.freeBlocks = map_free_size.at(dir_list[i]);
        }
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataDownload() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetAllSize() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetAppLaunchedUser() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetAutoUploadConditions() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetAutoUploadRequestInfo() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetAutoUploadSetting() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetBoundPsnAccountCount() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetEventInfo() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*,
                                             OrbisSaveDataEvent* event) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (event == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_TRACE(Lib_SaveData, "called");

    auto last_event = Backup::PopLastEvent();
    if (!last_event.has_value()) {
        return Error::NOT_FOUND;
    }

    event->type = last_event->origin;
    event->errorCode = 0;
    event->userId = last_event->user_id;
    event->titleId.data.FromString(last_event->title_id);
    event->dirName.data.FromString(last_event->dir_name);

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataGetFormat() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
                                           OrbisSaveDataMountInfo* info) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mountPoint == nullptr || info == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called");
    const std::string_view mount_point_str{mountPoint->data};
    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetMountPoint() == mount_point_str) {
            const u32 blocks = instance->GetMaxBlocks();
            const u64 size = Common::FS::GetDirectorySize(instance->GetSavePath());
            info->blocks = blocks;
            info->freeBlocks = blocks - size / OrbisSaveDataBlockSize;
            return Error::OK;
        }
    }
    return Error::NOT_MOUNTED;
}

Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
                                       OrbisSaveDataParamType paramType, void* paramBuf,
                                       size_t paramBufSize, size_t* gotSize) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType));
    const PSF* param_sfo = nullptr;

    const std::string_view mount_point_str{mountPoint->data};
    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetMountPoint() == mount_point_str) {
            param_sfo = &instance->GetParamSFO();
            break;
        }
    }
    if (param_sfo == nullptr) {
        return Error::NOT_MOUNTED;
    }

    switch (paramType) {
    case OrbisSaveDataParamType::ALL: {
        const auto param = static_cast<OrbisSaveDataParam*>(paramBuf);
        ASSERT(paramBufSize == sizeof(OrbisSaveDataParam));
        param->FromSFO(*param_sfo);
        if (gotSize != nullptr) {
            *gotSize = sizeof(OrbisSaveDataParam);
        }
        break;
    }
    case OrbisSaveDataParamType::TITLE:
    case OrbisSaveDataParamType::SUB_TITLE:
    case OrbisSaveDataParamType::DETAIL: {
        const auto param = static_cast<char*>(paramBuf);
        std::string_view key;
        if (paramType == OrbisSaveDataParamType::TITLE) {
            key = SaveParams::MAINTITLE;
        } else if (paramType == OrbisSaveDataParamType::SUB_TITLE) {
            key = SaveParams::SUBTITLE;
        } else if (paramType == OrbisSaveDataParamType::DETAIL) {
            key = SaveParams::DETAIL;
        } else {
            UNREACHABLE();
        }
        const size_t s = param_sfo->GetString(key).value_or("").copy(param, paramBufSize - 1);
        param[s] = '\0'; // null terminate
        if (gotSize != nullptr) {
            *gotSize = s + 1;
        }
    } break;
    case OrbisSaveDataParamType::USER_PARAM: {
        const auto param = static_cast<u32*>(paramBuf);
        *param = param_sfo->GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
        if (gotSize != nullptr) {
            *gotSize = sizeof(u32);
        }
    } break;
    case OrbisSaveDataParamType::MTIME: {
        const auto param = static_cast<time_t*>(paramBuf);
        const auto last_write = param_sfo->GetLastWrite().time_since_epoch();
        *param = chrono::duration_cast<chrono::seconds>(last_write).count();
        if (gotSize != nullptr) {
            *gotSize = sizeof(time_t);
        }
    } break;
    default:
        UNREACHABLE();
    }

    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (progress == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called");
    *progress = Backup::GetProgress();
    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf,
                                                const size_t bufSize, const int64_t offset) {
    LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2");
    OrbisSaveDataMemoryData data{};
    data.buf = buf;
    data.bufSize = bufSize;
    data.offset = offset;
    OrbisSaveDataMemoryGet2 param{};
    param.userId = userId;
    param.data = &data;
    param.param = nullptr;
    param.icon = nullptr;
    return sceSaveDataGetSaveDataMemory2(&param);
}

Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (getParam == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }

    u32 slot_id = 0;
    if (g_fw_ver > ElfInfo::FW_50) {
        slot_id = getParam->slotId;
    }
    if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
        LOG_INFO(Lib_SaveData, "called without save memory initialized");
        return Error::MEMORY_NOT_READY;
    }
    LOG_DEBUG(Lib_SaveData, "called");
    auto data = getParam->data;
    if (data != nullptr) {
        SaveMemory::ReadMemory(slot_id, data->buf, data->bufSize, data->offset);
    }
    auto param = getParam->param;
    if (param != nullptr) {
        param->FromSFO(SaveMemory::GetParamSFO(slot_id));
    }
    auto icon = getParam->icon;
    if (icon != nullptr) {
        auto icon_mem = SaveMemory::GetIcon(slot_id);
        size_t total = std::min(icon->bufSize, icon_mem.size());
        std::memcpy(icon->buf, icon_mem.data(), total);
        icon->dataSize = total;
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetSavePoint() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataInitialize(void*) {
    LOG_DEBUG(Lib_SaveData, "called");
    initialize();
    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataInitialize2(void*) {
    LOG_DEBUG(Lib_SaveData, "called");
    initialize();
    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataInitialize3(void*) {
    LOG_DEBUG(Lib_SaveData, "called");
    initialize();
    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataInitializeForCdlg() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataIsMounted() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint,
                                       OrbisSaveDataIcon* icon) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called");
    fs::path path;
    const std::string_view mount_point_str{mountPoint->data};
    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetMountPoint() == mount_point_str) {
            path = instance->GetIconPath();
            break;
        }
    }
    if (path.empty()) {
        return Error::NOT_MOUNTED;
    }
    if (!fs::exists(path)) {
        return Error::NOT_FOUND;
    }

    return icon->LoadIcon(path);
}

Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount,
                                    OrbisSaveDataMountResult* mount_result) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mount == nullptr && mount->dirName != nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}",
              mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks);

    OrbisSaveDataMount2 mount_info{};
    mount_info.userId = mount->userId;
    mount_info.dirName = mount->dirName;
    mount_info.mountMode = mount->mountMode;
    mount_info.blocks = mount->blocks;
    return saveDataMount(&mount_info, mount_result);
}

Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount,
                                     OrbisSaveDataMountResult* mount_result) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mount == nullptr && mount->dirName != nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}",
              mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks);
    return saveDataMount(mount, mount_result);
}

int PS4_SYSV_ABI sceSaveDataMount5() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataMountInternal() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataMountSys() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataPromote5() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataRebuildDatabase() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (restore == nullptr || restore->dirName == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }

    const std::string_view dir_name{restore->dirName->data};
    LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name);

    std::string_view title{restore->titleId != nullptr ? std::string_view{restore->titleId->data}
                                                       : std::string_view{g_game_serial}};

    const auto save_path = SaveInstance::MakeDirSavePath(restore->userId, title, dir_name);

    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetSavePath() == save_path) {
            return Error::BUSY;
        }
    }
    if (Backup::IsBackupExecutingFor(save_path)) {
        return Error::BACKUP_BUSY;
    }

    const auto backup_path = Backup::MakeBackupPath(save_path);
    if (!fs::exists(backup_path)) {
        return Error::NOT_FOUND;
    }

    const bool ok = Backup::Restore(save_path);
    if (!ok) {
        return Error::INTERNAL;
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint,
                                       const OrbisSaveDataIcon* icon) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called");
    fs::path path;
    const std::string_view mount_point_str{mountPoint->data};
    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetMountPoint() == mount_point_str) {
            if (instance->IsReadOnly()) {
                return Error::BAD_MOUNTED;
            }
            path = instance->GetIconPath();
            break;
        }
    }
    if (path.empty()) {
        return Error::NOT_MOUNTED;
    }

    try {
        const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Create);
        file.WriteRaw<u8>(icon->buf, std::min(icon->bufSize, icon->dataSize));
    } catch (const fs::filesystem_error& e) {
        LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());
        return Error::INTERNAL;
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataSetEventInfo() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint,
                                       OrbisSaveDataParamType paramType, const void* paramBuf,
                                       size_t paramBufSize) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr ||
        paramBuf == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType));
    PSF* param_sfo = nullptr;
    const std::string_view mount_point_str{mountPoint->data};
    for (auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetMountPoint() == mount_point_str) {
            param_sfo = &instance->GetParamSFO();
            break;
        }
    }
    if (param_sfo == nullptr) {
        return Error::NOT_MOUNTED;
    }

    switch (paramType) {
    case OrbisSaveDataParamType::ALL: {
        const auto param = static_cast<const OrbisSaveDataParam*>(paramBuf);
        ASSERT(paramBufSize == sizeof(OrbisSaveDataParam));
        param->ToSFO(*param_sfo);
        return Error::OK;
    } break;
    case OrbisSaveDataParamType::TITLE: {
        const auto value = static_cast<const char*>(paramBuf);
        param_sfo->AddString(std::string{SaveParams::MAINTITLE}, {value}, true);
    } break;
    case OrbisSaveDataParamType::SUB_TITLE: {
        const auto value = static_cast<const char*>(paramBuf);
        param_sfo->AddString(std::string{SaveParams::SUBTITLE}, {value}, true);
    } break;
    case OrbisSaveDataParamType::DETAIL: {
        const auto value = static_cast<const char*>(paramBuf);
        param_sfo->AddString(std::string{SaveParams::DETAIL}, {value}, true);
    } break;
    case OrbisSaveDataParamType::USER_PARAM: {
        const auto value = static_cast<const s32*>(paramBuf);
        param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, *value, true);
    } break;
    default:
        UNREACHABLE();
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
                                                size_t bufSize, int64_t offset) {
    LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2");
    OrbisSaveDataMemoryData data{};
    data.buf = buf;
    data.bufSize = bufSize;
    data.offset = offset;
    OrbisSaveDataMemorySet2 setParam{};
    setParam.userId = userId;
    setParam.data = &data;
    setParam.param = nullptr;
    setParam.icon = nullptr;
    return sceSaveDataSetSaveDataMemory2(&setParam);
}

Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (setParam == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    u32 slot_id = 0;
    u32 data_num = 1;
    if (g_fw_ver > ElfInfo::FW_50) {
        slot_id = setParam->slotId;
        if (setParam->dataNum > 1) {
            data_num = setParam->dataNum;
        }
    }
    if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
        LOG_INFO(Lib_SaveData, "called without save memory initialized");
        return Error::MEMORY_NOT_READY;
    }

    LOG_DEBUG(Lib_SaveData, "called");
    auto data = setParam->data;
    if (data != nullptr) {
        for (int i = 0; i < data_num; i++) {
            SaveMemory::WriteMemory(slot_id, data[i].buf, data[i].bufSize, data[i].offset);
        }
    }
    auto param = setParam->param;
    if (param != nullptr) {
        param->ToSFO(SaveMemory::GetParamSFO(slot_id));
        SaveMemory::SaveSFO(slot_id);
    }

    auto icon = setParam->icon;
    if (icon != nullptr) {
        SaveMemory::SetIcon(slot_id, icon->buf, icon->bufSize);
    }

    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
                                                  OrbisSaveDataParam* param) {
    LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize);
    OrbisSaveDataMemorySetup2 setupParam{};
    setupParam.userId = userId;
    setupParam.memorySize = memorySize;
    setupParam.initParam = nullptr;
    setupParam.initIcon = nullptr;
    OrbisSaveDataMemorySetupResult result{};
    const auto res = sceSaveDataSetupSaveDataMemory2(&setupParam, &result);
    if (res != Error::OK) {
        return res;
    }
    if (param != nullptr) {
        OrbisSaveDataMemorySet2 setParam{};
        setParam.userId = userId;
        setParam.data = nullptr;
        setParam.param = param;
        setParam.icon = nullptr;
        sceSaveDataSetSaveDataMemory2(&setParam);
    }
    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
                                                   OrbisSaveDataMemorySetupResult* result) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (setupParam == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called");

    u32 slot_id = 0;
    if (g_fw_ver > ElfInfo::FW_50) {
        slot_id = setupParam->slotId;
    }

    const auto& save_path = SaveMemory::GetSavePath(setupParam->userId, slot_id, g_game_serial);
    for (const auto& instance : g_mount_slots) {
        if (instance.has_value() && instance->GetSavePath() == save_path) {
            return Error::BUSY;
        }
    }

    try {
        size_t existed_size = SaveMemory::SetupSaveMemory(setupParam->userId, slot_id,
                                                          g_game_serial, setupParam->memorySize);
        if (existed_size == 0) { // Just created
            if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) {
                auto& sfo = SaveMemory::GetParamSFO(slot_id);
                setupParam->initParam->ToSFO(sfo);
            }
            SaveMemory::SaveSFO(slot_id);

            auto init_icon = setupParam->initIcon;
            if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) {
                SaveMemory::SetIcon(slot_id, init_icon->buf, init_icon->bufSize);
            } else {
                SaveMemory::SetIcon(slot_id);
            }
        }
        if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) {
            result->existedMemorySize = existed_size;
        }
    } catch (const fs::filesystem_error& e) {
        LOG_ERROR(Lib_SaveData, "Failed to create/load save memory: {}", e.what());

        const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{
            .type = MsgDialog::ButtonType::OK,
            .msg = "Failed to create or load save memory:\n" + std::string{e.what()},
        }};
        MsgDialog::ShowMsgDialog(dialog);

        return Error::INTERNAL;
    }

    return Error::OK;
}

int PS4_SYSV_ABI sceSaveDataShutdownStart() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataSyncCloudList() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) {
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (syncParam == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }

    u32 slot_id = 0;
    if (g_fw_ver > ElfInfo::FW_50) {
        slot_id = syncParam->slotId;
    }

    if (!SaveMemory::IsSaveMemoryInitialized(slot_id)) {
        LOG_INFO(Lib_SaveData, "called without save memory initialized");
        return Error::MEMORY_NOT_READY;
    }
    LOG_DEBUG(Lib_SaveData, "called");

    SaveMemory::PersistMemory(slot_id);
    const auto& save_path = SaveMemory::GetSaveDir(slot_id);
    Backup::NewRequest(syncParam->userId, g_game_serial, save_path,
                       OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC);

    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataTerminate() {
    LOG_DEBUG(Lib_SaveData, "called");
    if (!g_initialized) {
        return setNotInitializedError();
    }
    for (auto& instance : g_mount_slots) {
        if (instance.has_value()) {
            if (g_fw_ver >= ElfInfo::FW_40) {
                return Error::BUSY;
            }
            instance->Umount();
            instance.reset();
        }
    }
    g_initialized = false;
    Backup::StopThread();
    return Error::OK;
}

Error PS4_SYSV_ABI sceSaveDataTransferringMount(const OrbisSaveDataTransferringMount* mount,
                                                OrbisSaveDataMountResult* mountResult) {
    LOG_DEBUG(Lib_SaveData, "called");
    if (!g_initialized) {
        LOG_INFO(Lib_SaveData, "called without initialize");
        return setNotInitializedError();
    }
    if (mount == nullptr || mount->titleId == nullptr || mount->dirName == nullptr) {
        LOG_INFO(Lib_SaveData, "called with invalid parameter");
        return Error::PARAMETER;
    }
    LOG_DEBUG(Lib_SaveData, "called titleId: {}, dirName: {}", mount->titleId->data.to_view(),
              mount->dirName->data.to_view());
    OrbisSaveDataMount2 mount_info{};
    mount_info.userId = mount->userId;
    mount_info.dirName = mount->dirName;
    mount_info.mountMode = OrbisSaveDataMountMode::RDONLY;
    return saveDataMount(&mount_info, mountResult, mount->titleId->data.to_string());
}

Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) {
    LOG_DEBUG(Lib_SaveData, "called");
    return Umount(mountPoint);
}

int PS4_SYSV_ABI sceSaveDataUmountSys() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) {
    LOG_DEBUG(Lib_SaveData, "called");
    return Umount(mountPoint, true);
}

int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI sceSaveDataUpload() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

int PS4_SYSV_ABI Func_02E4C4D201716422() {
    LOG_ERROR(Lib_SaveData, "(STUBBED) called");
    return ORBIS_OK;
}

void RegisterLib(Core::Loader::SymbolsResolver* sym) {
    LIB_FUNCTION("dQ2GohUHXzk", "libSceSaveData", 1, "libSceSaveData", sceSaveDataAbort);
    LIB_FUNCTION("z1JA8-iJt3k", "libSceSaveData", 1, "libSceSaveData", sceSaveDataBackup);
    LIB_FUNCTION("kLJQ3XioYiU", "libSceSaveData", 1, "libSceSaveData", sceSaveDataBindPsnAccount);
    LIB_FUNCTION("hHHCPRqA3+g", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataBindPsnAccountForSystemBackup);
    LIB_FUNCTION("ykwIZfVD08s", "libSceSaveData", 1, "libSceSaveData", sceSaveDataChangeDatabase);
    LIB_FUNCTION("G0hFeOdRCUs", "libSceSaveData", 1, "libSceSaveData", sceSaveDataChangeInternal);
    LIB_FUNCTION("RQOqDbk3bSU", "libSceSaveData", 1, "libSceSaveData", sceSaveDataCheckBackupData);
    LIB_FUNCTION("rYvLW1z2poM", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataCheckBackupDataForCdlg);
    LIB_FUNCTION("v1TrX+3ZB10", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataCheckBackupDataInternal);
    LIB_FUNCTION("-eczr5e4dsI", "libSceSaveData", 1, "libSceSaveData", sceSaveDataCheckCloudData);
    LIB_FUNCTION("4OPOZxfVkHA", "libSceSaveData", 1, "libSceSaveData", sceSaveDataCheckIpmiIfSize);
    LIB_FUNCTION("1i0rfc+mfa8", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataCheckSaveDataBroken);
    LIB_FUNCTION("p6A1adyQi3E", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataCheckSaveDataVersion);
    LIB_FUNCTION("S49B+I96kpk", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataCheckSaveDataVersionLatest);
    LIB_FUNCTION("Wz-4JZfeO9g", "libSceSaveData", 1, "libSceSaveData", sceSaveDataClearProgress);
    LIB_FUNCTION("YbCO38BOOl4", "libSceSaveData", 1, "libSceSaveData", sceSaveDataCopy5);
    LIB_FUNCTION("kbIIP9aXK9A", "libSceSaveData", 1, "libSceSaveData", sceSaveDataCreateUploadData);
    LIB_FUNCTION("gW6G4HxBBXA", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDebug);
    LIB_FUNCTION("bYCnxLexU7M", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDebugCleanMount);
    LIB_FUNCTION("hVDqYB8+jkk", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataDebugCompiledSdkVersion);
    LIB_FUNCTION("K9gXXlrVLNI", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataDebugCreateSaveDataRoot);
    LIB_FUNCTION("5yHFvMwZX2o", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDebugGetThreadId);
    LIB_FUNCTION("UGTldPVEdB4", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataDebugRemoveSaveDataRoot);
    LIB_FUNCTION("AYBQmnRplrg", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDebugTarget);
    LIB_FUNCTION("S1GkePI17zQ", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDelete);
    LIB_FUNCTION("SQWusLoK8Pw", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDelete5);
    LIB_FUNCTION("pJrlpCgR8h4", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDeleteAllUser);
    LIB_FUNCTION("fU43mJUgKcM", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDeleteCloudData);
    LIB_FUNCTION("uZqc4JpFdeY", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDeleteUser);
    LIB_FUNCTION("dyIhnXq-0SM", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDirNameSearch);
    LIB_FUNCTION("xJ5NFWC3m+k", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataDirNameSearchInternal);
    LIB_FUNCTION("h1nP9EYv3uc", "libSceSaveData", 1, "libSceSaveData", sceSaveDataDownload);
    LIB_FUNCTION("A1ThglSGUwA", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetAllSize);
    LIB_FUNCTION("KuXcrMAQIMQ", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetAppLaunchedUser);
    LIB_FUNCTION("itZ46iH14Vs", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetAutoUploadConditions);
    LIB_FUNCTION("PL20kjAXZZ4", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetAutoUploadRequestInfo);
    LIB_FUNCTION("G12foE0S77E", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetAutoUploadSetting);
    LIB_FUNCTION("PzDtD6eBXIM", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetBoundPsnAccountCount);
    LIB_FUNCTION("tu0SDPl+h88", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetClientThreadPriority);
    LIB_FUNCTION("6lZYZqQPfkY", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetCloudQuotaInfo);
    LIB_FUNCTION("CWlBd2Ay1M4", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetDataBaseFilePath);
    LIB_FUNCTION("eBSSNIG6hMk", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetEventInfo);
    LIB_FUNCTION("j8xKtiFj0SY", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetEventResult);
    LIB_FUNCTION("UMpxor4AlKQ", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetFormat);
    LIB_FUNCTION("pc4guaUPVqA", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetMountedSaveDataCount);
    LIB_FUNCTION("65VH0Qaaz6s", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetMountInfo);
    LIB_FUNCTION("XgvSuIdnMlw", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetParam);
    LIB_FUNCTION("ANmSWUiyyGQ", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetProgress);
    LIB_FUNCTION("SN7rTPHS+Cg", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetSaveDataCount);
    LIB_FUNCTION("7Bt5pBC-Aco", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetSaveDataMemory);
    LIB_FUNCTION("QwOO7vegnV8", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetSaveDataMemory2);
    LIB_FUNCTION("+bRDRotfj0Y", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetSaveDataRootDir);
    LIB_FUNCTION("3luF0xq0DkQ", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetSaveDataRootPath);
    LIB_FUNCTION("DwAvlQGvf1o", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetSaveDataRootUsbPath);
    LIB_FUNCTION("kb24-4DLyNo", "libSceSaveData", 1, "libSceSaveData", sceSaveDataGetSavePoint);
    LIB_FUNCTION("OYmnApJ9q+U", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataGetUpdatedDataCount);
    LIB_FUNCTION("ZkZhskCPXFw", "libSceSaveData", 1, "libSceSaveData", sceSaveDataInitialize);
    LIB_FUNCTION("l1NmDeDpNGU", "libSceSaveData", 1, "libSceSaveData", sceSaveDataInitialize2);
    LIB_FUNCTION("TywrFKCoLGY", "libSceSaveData", 1, "libSceSaveData", sceSaveDataInitialize3);
    LIB_FUNCTION("g9uwUI3BlQ8", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataInitializeForCdlg);
    LIB_FUNCTION("voAQW45oKuo", "libSceSaveData", 1, "libSceSaveData", sceSaveDataIsDeletingUsbDb);
    LIB_FUNCTION("ieP6jP138Qo", "libSceSaveData", 1, "libSceSaveData", sceSaveDataIsMounted);
    LIB_FUNCTION("cGjO3wM3V28", "libSceSaveData", 1, "libSceSaveData", sceSaveDataLoadIcon);
    LIB_FUNCTION("32HQAQdwM2o", "libSceSaveData", 1, "libSceSaveData", sceSaveDataMount);
    LIB_FUNCTION("0z45PIH+SNI", "libSceSaveData", 1, "libSceSaveData", sceSaveDataMount2);
    LIB_FUNCTION("xz0YMi6BfNk", "libSceSaveData", 1, "libSceSaveData", sceSaveDataMount5);
    LIB_FUNCTION("msCER7Iibm8", "libSceSaveData", 1, "libSceSaveData", sceSaveDataMountInternal);
    LIB_FUNCTION("-XYmdxjOqyA", "libSceSaveData", 1, "libSceSaveData", sceSaveDataMountSys);
    LIB_FUNCTION("uNu7j3pL2mQ", "libSceSaveData", 1, "libSceSaveData", sceSaveDataPromote5);
    LIB_FUNCTION("SgIY-XYA2Xg", "libSceSaveData", 1, "libSceSaveData", sceSaveDataRebuildDatabase);
    LIB_FUNCTION("hsKd5c21sQc", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataRegisterEventCallback);
    LIB_FUNCTION("lU9YRFsgwSU", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataRestoreBackupData);
    LIB_FUNCTION("HuToUt1GQ8w", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataRestoreBackupDataForCdlg);
    LIB_FUNCTION("aoZKKNjlq3Y", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataRestoreLoadSaveDataMemory);
    LIB_FUNCTION("c88Yy54Mx0w", "libSceSaveData", 1, "libSceSaveData", sceSaveDataSaveIcon);
    LIB_FUNCTION("0VFHv-Fa4w8", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSetAutoUploadSetting);
    LIB_FUNCTION("52pL2GKkdjA", "libSceSaveData", 1, "libSceSaveData", sceSaveDataSetEventInfo);
    LIB_FUNCTION("85zul--eGXs", "libSceSaveData", 1, "libSceSaveData", sceSaveDataSetParam);
    LIB_FUNCTION("v3vg2+cooYw", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSetSaveDataLibraryUser);
    LIB_FUNCTION("h3YURzXGSVQ", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSetSaveDataMemory);
    LIB_FUNCTION("cduy9v4YmT4", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSetSaveDataMemory2);
    LIB_FUNCTION("v7AAAMo0Lz4", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSetupSaveDataMemory);
    LIB_FUNCTION("oQySEUfgXRA", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSetupSaveDataMemory2);
    LIB_FUNCTION("zMgXM79jRhw", "libSceSaveData", 1, "libSceSaveData", sceSaveDataShutdownStart);
    LIB_FUNCTION("+orZm32HB1s", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSupportedFakeBrokenStatus);
    LIB_FUNCTION("LMSQUTxmGVg", "libSceSaveData", 1, "libSceSaveData", sceSaveDataSyncCloudList);
    LIB_FUNCTION("wiT9jeC7xPw", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataSyncSaveDataMemory);
    LIB_FUNCTION("yKDy8S5yLA0", "libSceSaveData", 1, "libSceSaveData", sceSaveDataTerminate);
    LIB_FUNCTION("WAzWTZm1H+I", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataTransferringMount);
    LIB_FUNCTION("BMR4F-Uek3E", "libSceSaveData", 1, "libSceSaveData", sceSaveDataUmount);
    LIB_FUNCTION("2-8NWLS8QSA", "libSceSaveData", 1, "libSceSaveData", sceSaveDataUmountSys);
    LIB_FUNCTION("VwadwBBBJ80", "libSceSaveData", 1, "libSceSaveData", sceSaveDataUmountWithBackup);
    LIB_FUNCTION("v-AK1AxQhS0", "libSceSaveData", 1, "libSceSaveData",
                 sceSaveDataUnregisterEventCallback);
    LIB_FUNCTION("COwz3WBj+5s", "libSceSaveData", 1, "libSceSaveData", sceSaveDataUpload);
    LIB_FUNCTION("AuTE0gFxZCI", "libSceSaveData", 1, "libSceSaveData", Func_02E4C4D201716422);
};

} // namespace Libraries::SaveData
