1
0
mirror of https://github.com/citra-emu/citra.git synced 2025-05-18 14:10:08 +00:00
citra/src/core/file_sys/archive_extsavedata.cpp
Rokkubro b5ff2440f6 Squash commits during rebase again:
Final things from review comments

Clang format

Shorten property code (similar to ) and move to utils

Move all additional helper functions into a 'utils' file. Simplify some things as requested in review

Minor code changes from review before rebase

fix misusing std span

Fix leftovers from rebase, and null-terminator problem with download string-view

Change downloadbossdatafromurl to take in string_views, make getting the list of files more dynamic

Fix error in linux builds and cleanup

Squash commits during rebase:

Some changes as per review and cleanup

More changes as per review

Changes as per review

Futures cannot be copied, remove stubbed warning on implemented calls, remove unneeded task_status simulation, simplify getting task_status and duration

Implement downloading tasks in background

Very final changes from review; and use common for converting strings

FInal changes for review

Attempt to fix codecvt error

Use references when any_casting

Update boost submodule to use master again, refactor how properties work, other minor changes per review

Fix operator overload error on linux/mingw

Make some changes as requested by review; change boost submodule url temporarily to use boost.url

Fix for android build

Fixes android builds when web services are enabled, like in 

Avoid crashes when urls are invalid

clang-format

Return error status properly on task fail

Fix implementation of gettaskstate, gettaskstatus and gettaskservicestatus

Fix mingw build error

Add support for reading tasks from boss save data databases.

clang-format

Implement storing task properties

Fix missing includes and add references in loops

Change task_id_list to map, initial implementation of task properties, minor refactor

Remove the dependency on the newer behavior of std erase to fix android building

Fix compilation on android and other platforms when web services are not enabled

Fix clang-format errors

Add support for downloading and decrypting spotpass data directly from nintendo servers

Fix windows implicit conversion error again

Fix comment

Fix filter in NsDataIdList; Finish GetNsDataHeaderInfo; Implement basic support for registering tasks and checking if they exist TODO actually read and write from boss savedata dbs

Add boss extdata to archive.h so the lle boss module can function properly

Implement ReadNsData and partially implement GetNsDataHeaderInfo and GetNsDataLastUpdate; MK7 now reads spotpass data and successfully boots!

Made requested changes; added filtering; removed readnsdata implementation

Add partial implementations of GetNsDataIdList(1/2/3) and ReadNsData

Add zeroed array of nsdataid entries, run clang-format

Check the spotpass extdata directory to determine number of ns output entries

Check for PLvPWAA

Only set the number of output entries in GetNsDataIdList1 to 1 if PLvPWAA is detected.

Fix plvpwaa dlc error

Return 1 for the number of output entries in the GetNsDataIdList1 stub. This fixes the extra content for Professor Layton vs Phoenix Wright Ace Attorney as the game expects the boss extdata to not be empty. Might break other games if they attempt to do anything with the ns data. (although the readnsdata and deletensdata methods are both still stubbed)
2023-11-02 21:09:42 +11:00

316 lines
11 KiB
C++

// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <vector>
#include <fmt/format.h>
#include "common/archives.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/disk_archive.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/path_parser.h"
#include "core/file_sys/savedata_archive.h"
#include "core/hle/service/fs/archive.h"
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_ExtSaveData)
namespace FileSys {
/**
* A modified version of DiskFile for fixed-size file used by ExtSaveData
* The file size can't be changed by SetSize or Write.
*/
class FixSizeDiskFile : public DiskFile {
public:
FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode,
std::unique_ptr<DelayGenerator> delay_generator_)
: DiskFile(std::move(file), mode, std::move(delay_generator_)) {
size = GetSize();
}
bool SetSize(u64 size) const override {
return false;
}
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
const u8* buffer) override {
if (offset > size) {
return ERR_WRITE_BEYOND_END;
} else if (offset == size) {
return 0ULL;
}
if (offset + length > size) {
length = size - offset;
}
return DiskFile::Write(offset, length, flush, buffer);
}
private:
u64 size{};
};
class ExtSaveDataDelayGenerator : public DelayGenerator {
public:
u64 GetReadDelayNs(std::size_t length) override {
// This is the delay measured for a savedata read,
// not for extsaveData
// For now we will take that
static constexpr u64 slope(183);
static constexpr u64 offset(524879);
static constexpr u64 minimum(631826);
u64 ipc_delay_nanoseconds =
std::max<u64>(static_cast<u64>(length) * slope + offset, minimum);
return ipc_delay_nanoseconds;
}
u64 GetOpenDelayNs() override {
// This is the delay measured on N3DS with
// https://gist.github.com/FearlessTobi/929b68489f4abb2c6cf81d56970a20b4
// from the results the average of each length was taken.
static constexpr u64 IPCDelayNanoseconds(3085068);
return IPCDelayNanoseconds;
}
SERIALIZE_DELAY_GENERATOR
};
/**
* Archive backend for general extsave data archive type.
* The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for
* - file size can't be changed once created (thus creating zero-size file and openning with create
* flag are prohibited);
* - always open a file with read+write permission.
*/
class ExtSaveDataArchive : public SaveDataArchive {
public:
explicit ExtSaveDataArchive(const std::string& mount_point,
std::unique_ptr<DelayGenerator> delay_generator_)
: SaveDataArchive(mount_point) {
delay_generator = std::move(delay_generator_);
}
std::string GetName() const override {
return "ExtSaveDataArchive: " + mount_point;
}
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
const Mode& mode) const override {
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
const PathParser path_parser(path);
if (!path_parser.IsValid()) {
LOG_ERROR(Service_FS, "Invalid path {}", path.DebugStr());
return ERROR_INVALID_PATH;
}
if (mode.hex == 0) {
LOG_ERROR(Service_FS, "Empty open mode");
return ERROR_UNSUPPORTED_OPEN_FLAGS;
}
if (mode.create_flag) {
LOG_ERROR(Service_FS, "Create flag is not supported");
return ERROR_UNSUPPORTED_OPEN_FLAGS;
}
const auto full_path = path_parser.BuildHostPath(mount_point);
switch (path_parser.GetHostStatus(mount_point)) {
case PathParser::InvalidMountPoint:
LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point {}", mount_point);
return ERROR_FILE_NOT_FOUND;
case PathParser::PathNotFound:
LOG_ERROR(Service_FS, "Path not found {}", full_path);
return ERROR_PATH_NOT_FOUND;
case PathParser::FileInPath:
case PathParser::DirectoryFound:
LOG_ERROR(Service_FS, "Unexpected file or directory in {}", full_path);
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
case PathParser::NotFound:
LOG_ERROR(Service_FS, "{} not found", full_path);
return ERROR_FILE_NOT_FOUND;
case PathParser::FileFound:
break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, "r+b");
if (!file.IsOpen()) {
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path);
return ERROR_FILE_NOT_FOUND;
}
Mode rwmode;
rwmode.write_flag.Assign(1);
rwmode.read_flag.Assign(1);
auto delay_generator = std::make_unique<ExtSaveDataDelayGenerator>();
return std::make_unique<FixSizeDiskFile>(std::move(file), rwmode,
std::move(delay_generator));
}
ResultCode CreateFile(const Path& path, u64 size) const override {
if (size == 0) {
LOG_ERROR(Service_FS, "Zero-size file is not supported");
return ERROR_UNSUPPORTED_OPEN_FLAGS;
}
return SaveDataArchive::CreateFile(path, size);
}
private:
ExtSaveDataArchive() = default;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<SaveDataArchive>(*this);
}
friend class boost::serialization::access;
};
struct ExtSaveDataArchivePath {
u32_le media_type;
u32_le save_low;
u32_le save_high;
};
static_assert(sizeof(ExtSaveDataArchivePath) == 12, "Incorrect path size");
std::string GetExtSaveDataPath(std::string_view mount_point, const Path& path) {
std::vector<u8> vec_data = path.AsBinary();
ExtSaveDataArchivePath path_data;
std::memcpy(&path_data, vec_data.data(), sizeof(path_data));
return fmt::format("{}{:08X}/{:08X}/", mount_point, path_data.save_high, path_data.save_low);
}
std::string GetExtDataContainerPath(std::string_view mount_point, bool shared) {
if (shared) {
return fmt::format("{}data/{}/extdata/", mount_point, SYSTEM_ID);
}
return fmt::format("{}Nintendo 3DS/{}/{}/extdata/", mount_point, SYSTEM_ID, SDCARD_ID);
}
std::string GetExtDataPathFromId(std::string_view mount_point, u64 extdata_id) {
const u32 high = static_cast<u32>(extdata_id >> 32);
const u32 low = static_cast<u32>(extdata_id & 0xFFFFFFFF);
return fmt::format("{}{:08x}/{:08x}/", GetExtDataContainerPath(mount_point, false), high, low);
}
Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) {
ExtSaveDataArchivePath path;
path.media_type = media_type;
path.save_high = high;
path.save_low = low;
std::vector<u8> binary_path(sizeof(path));
std::memcpy(binary_path.data(), &path, binary_path.size());
return {binary_path};
}
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location,
bool shared)
: shared(shared), boss(false), mount_point(GetExtDataContainerPath(mount_location, shared)) {
LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point);
}
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location,
bool shared, bool boss)
: shared(shared), boss(boss), mount_point(GetExtDataContainerPath(mount_location, shared)) {
LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point);
}
Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) {
if (!shared)
return path;
static constexpr u32 SharedExtDataHigh = 0x48000;
ExtSaveDataArchivePath new_path;
std::memcpy(&new_path, path.AsBinary().data(), sizeof(new_path));
// The FS module overwrites the high value of the saveid when dealing with the SharedExtSaveData
// archive.
new_path.save_high = SharedExtDataHigh;
std::vector<u8> binary_data(sizeof(new_path));
std::memcpy(binary_data.data(), &new_path, binary_data.size());
return {binary_data};
}
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
u64 program_id) {
std::string fullpath =
GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + (boss ? "boss/" : "user/");
if (!FileUtil::Exists(fullpath)) {
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
if (!shared) {
return ERR_NOT_FOUND_INVALID_STATE;
} else {
return ERR_NOT_FORMATTED;
}
}
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<ExtSaveDataDelayGenerator>();
return std::make_unique<ExtSaveDataArchive>(fullpath, std::move(delay_generator));
}
ResultCode ArchiveFactory_ExtSaveData::Format(const Path& path,
const FileSys::ArchiveFormatInfo& format_info,
u64 program_id) {
auto corrected_path = GetCorrectedPath(path);
// These folders are always created with the ExtSaveData
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
FileUtil::CreateFullPath(user_path);
FileUtil::CreateFullPath(boss_path);
// Write the format metadata
std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata";
FileUtil::IOFile file(metadata_path, "wb");
if (!file.IsOpen()) {
// TODO(Subv): Find the correct error code
return RESULT_UNKNOWN;
}
file.WriteBytes(&format_info, sizeof(format_info));
return RESULT_SUCCESS;
}
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path,
u64 program_id) const {
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata";
FileUtil::IOFile file(metadata_path, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
// TODO(Subv): Verify error code
return ERR_NOT_FORMATTED;
}
ArchiveFormatInfo info = {};
file.ReadBytes(&info, sizeof(info));
return info;
}
void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, std::span<const u8> icon) {
std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path);
FileUtil::IOFile icon_file(game_path + "icon", "wb");
icon_file.WriteBytes(icon.data(), icon.size());
}
} // namespace FileSys
SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator)
SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataArchive)