Initial Savedata Implementation (#75)

* Rework VFS to support creating and writing files and introduce OsFileSystem
OsFileSystem abstracts a directory on the device using the filesystem API.
This also introduces GetEntryType and changes FileExists to use it.

* Implement the Horizon FileSystem APIs using our VFS framework
Horizon provides access to files through its IFileSystem class, we can
closely map this to our vfs::FileSystem class.

* Add support for creating application savedata
This implements basic savedata creation using the OsFileSystem API. The
data is stored in Skyline's private directory is stored in the same
format as yuzu.
This commit is contained in:
Billy Laws 2020-08-08 20:38:51 +01:00 committed by GitHub
parent f72b81fcea
commit 6edf89b538
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 585 additions and 65 deletions

View File

@ -97,6 +97,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/timesrv/ITimeZoneService.cpp
${source_DIR}/skyline/services/fssrv/IFileSystemProxy.cpp
${source_DIR}/skyline/services/fssrv/IFileSystem.cpp
${source_DIR}/skyline/services/fssrv/IFile.cpp
${source_DIR}/skyline/services/fssrv/IStorage.cpp
${source_DIR}/skyline/services/nvdrv/INvDrvServices.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvmap.cpp
@ -129,6 +130,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/socket/bsd/IClient.cpp
${source_DIR}/skyline/services/ssl/ISslService.cpp
${source_DIR}/skyline/services/prepo/IPrepoService.cpp
${source_DIR}/skyline/vfs/os_filesystem.cpp
${source_DIR}/skyline/vfs/partition_filesystem.cpp
${source_DIR}/skyline/vfs/rom_filesystem.cpp
${source_DIR}/skyline/vfs/os_backing.cpp

View File

@ -24,7 +24,7 @@ void signalHandler(int signal) {
FaultCount++;
}
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jint logFd) {
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jstring appFilesPathJstring) {
Halt = false;
FaultCount = 0;
fps = 0;
@ -41,13 +41,17 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
auto jvmManager = std::make_shared<skyline::JvmManager>(env, instance);
auto settings = std::make_shared<skyline::Settings>(preferenceFd);
auto logger = std::make_shared<skyline::Logger>(logFd, static_cast<skyline::Logger::LogLevel>(std::stoi(settings->GetString("log_level"))));
auto appFilesPath = env->GetStringUTFChars(appFilesPathJstring, nullptr);
auto logger = std::make_shared<skyline::Logger>(std::string(appFilesPath) + "skyline.log", static_cast<skyline::Logger::LogLevel>(std::stoi(settings->GetString("log_level"))));
//settings->List(logger); // (Uncomment when you want to print out all settings strings)
auto start = std::chrono::steady_clock::now();
try {
skyline::kernel::OS os(jvmManager, logger, settings);
skyline::kernel::OS os(jvmManager, logger, settings, std::string(appFilesPath));
env->ReleaseStringUTFChars(appFilesPathJstring, appFilesPath);
auto romUri = env->GetStringUTFChars(romUriJstring, nullptr);
logger->Info("Launching ROM {}", romUri);
env->ReleaseStringUTFChars(romUriJstring, romUri);
@ -86,4 +90,4 @@ extern "C" JNIEXPORT jint Java_emu_skyline_EmulationActivity_getFps(JNIEnv *env,
extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIEnv *env, jobject thiz) {
return static_cast<float>(frametime) / 100;
}
}

View File

@ -120,8 +120,8 @@ namespace skyline {
logger->Info("Key: {}, Value: {}, Type: Bool", iter.first, GetBool(iter.first));
}
Logger::Logger(int fd, LogLevel configLevel) : configLevel(configLevel) {
logFile.__open(fd, std::ios::app);
Logger::Logger(const std::string &path, LogLevel configLevel) : configLevel(configLevel) {
logFile.open(path, std::ios::app);
WriteHeader("Logging started");
}

View File

@ -38,6 +38,8 @@ namespace skyline {
// Status codes
namespace status {
constexpr u32 Success = 0x0; //!< "Success"
constexpr u32 PathDoesNotExist = 0x202; //!< "Path does not exist"
constexpr u32 GenericError = 0x272; //!< "Generic error"
constexpr u32 NoMessages = 0x680; //!< "No message available"
constexpr u32 ServiceInvName = 0xC15; //!< "Invalid name"
constexpr u32 ServiceNotReg = 0xE15; //!< "Service not registered"
@ -220,10 +222,10 @@ namespace skyline {
LogLevel configLevel; //!< The level of logs to write
/**
* @param fd A FD to the log file
* @param path The path of the log file
* @param configLevel The minimum level of logs to write
*/
Logger(int fd, LogLevel configLevel);
Logger(const std::string &path, LogLevel configLevel);
/**
* @brief Writes the termination message to the log file

View File

@ -10,7 +10,7 @@
#include "os.h"
namespace skyline::kernel {
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state) {}
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, const std::string &appFilesPath) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state), appFilesPath(appFilesPath) {}
void OS::Execute(int romFd, loader::RomFormat romType) {
auto romFile = std::make_shared<vfs::OsBacking>(romFd);

View File

@ -25,13 +25,14 @@ namespace skyline::kernel {
std::shared_ptr<type::KProcess> process; //!< The KProcess object for the emulator, representing the guest process
service::ServiceManager serviceManager; //!< This manages all of the service functions
MemoryManager memory; //!< The MemoryManager object for this process
std::string appFilesPath; //!< The full path to the app's files directory
/**
* @param logger An instance of the Logger class
* @param settings An instance of the Settings class
* @param window The ANativeWindow object to draw the screen to
*/
OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings);
OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, const std::string &appFilesPath);
/**
* @brief Execute a particular ROM file. This launches the main process and calls the NCE class to handle execution.

View File

@ -58,6 +58,7 @@ namespace skyline::service {
timesrv_ISteadyClock,
fssrv_IFileSystemProxy,
fssrv_IFileSystem,
fssrv_IFile,
fssrv_IStorage,
nvdrv_INvDrvServices,
visrv_IManagerRootService,

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include "IFile.h"
namespace skyline::service::fssrv {
IFile::IFile(std::shared_ptr<vfs::Backing> &backing, const DeviceState &state, ServiceManager &manager) : backing(backing), BaseService(state, manager, Service::fssrv_IFile, "fssrv:IFile", {
{0x0, SFUNC(IFile::Read)},
{0x1, SFUNC(IFile::Write)},
{0x3, SFUNC(IFile::SetSize)},
{0x4, SFUNC(IFile::GetSize)}
}) {}
void IFile::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto readOption = request.Pop<u32>();
request.Skip<u32>();
auto offset = request.Pop<i64>();
auto size = request.Pop<i64>();
if (offset < 0) {
state.logger->Warn("Trying to read a file with a negative offset");
response.errorCode = constant::status::InvAddress;
return;
}
if (size < 0) {
state.logger->Warn("Trying to read a file with a negative size");
response.errorCode = constant::status::InvSize;
return;
}
response.Push<u32>(static_cast<u32>(backing->Read(state.process->GetPointer<u8>(request.outputBuf.at(0).address), offset, size)));
}
void IFile::Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto writeOption = request.Pop<u32>();
request.Skip<u32>();
auto offset = request.Pop<i64>();
auto size = request.Pop<i64>();
if (offset < 0) {
state.logger->Warn("Trying to write to a file with a negative offset");
response.errorCode = constant::status::InvAddress;
return;
}
if (size < 0) {
state.logger->Warn("Trying to write to a file with a negative size");
response.errorCode = constant::status::InvSize;
return;
}
if (request.inputBuf.at(0).size < size) {
state.logger->Warn("The input buffer is not large enough to fit the requested size");
response.errorCode = constant::status::InvSize;
return;
}
if (backing->Write(state.process->GetPointer<u8>(request.inputBuf.at(0).address), offset, request.inputBuf.at(0).size) != size) {
state.logger->Warn("Failed to write all data to the backing");
response.errorCode = constant::status::GenericError;
}
}
void IFile::SetSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
backing->Resize(request.Pop<u64>());
}
void IFile::GetSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u64>(backing->size);
}
}

View File

@ -0,0 +1,41 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/base_service.h>
#include <services/serviceman.h>
#include <vfs/backing.h>
namespace skyline::service::fssrv {
/**
* @brief IFile is an interface for accessing files (https://switchbrew.org/wiki/Filesystem_services#IFile)
*/
class IFile : public BaseService {
private:
std::shared_ptr<vfs::Backing> backing; //!< The backing of the IFile
public:
IFile(std::shared_ptr<vfs::Backing> &backing, const DeviceState &state, ServiceManager &manager);
/**
* @brief This reads a buffer from a region of an IFile
*/
void Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This writes a buffer to a region of an IFile
*/
void Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This sets the size of an IFile
*/
void SetSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This obtains the size of an IFile
*/
void GetSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
}

View File

@ -1,8 +1,55 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include <vfs/filesystem.h>
#include "IFile.h"
#include "IFileSystem.h"
namespace skyline::service::fssrv {
IFileSystem::IFileSystem(FsType type, const DeviceState &state, ServiceManager &manager) : type(type), BaseService(state, manager, Service::fssrv_IFileSystem, "fssrv:IFileSystem", {}) {}
IFileSystem::IFileSystem(std::shared_ptr<vfs::FileSystem> backing, const DeviceState &state, ServiceManager &manager) : backing(backing), BaseService(state, manager, Service::fssrv_IFileSystem, "fssrv:IFileSystem", {
{0x0, SFUNC(IFileSystem::CreateFile)},
{0x7, SFUNC(IFileSystem::GetEntryType)},
{0x8, SFUNC(IFileSystem::OpenFile)},
{0xa, SFUNC(IFileSystem::Commit)}
}) {}
void IFileSystem::CreateFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path = std::string(state.process->GetPointer<char>(request.inputBuf.at(0).address));
auto mode = request.Pop<u64>();
auto size = request.Pop<u32>();
response.errorCode = backing->CreateFile(path, size) ? constant::status::Success : constant::status::PathDoesNotExist;
}
void IFileSystem::GetEntryType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path = std::string(state.process->GetPointer<char>(request.inputBuf.at(0).address));
auto type = backing->GetEntryType(path);
if (type.has_value()) {
response.Push(type.value());
} else {
response.Push<u32>(0);
response.errorCode = constant::status::PathDoesNotExist;
}
}
void IFileSystem::OpenFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::string path = std::string(state.process->GetPointer<char>(request.inputBuf.at(0).address));
auto mode = request.Pop<vfs::Backing::Mode>();
if (!backing->FileExists(path)) {
response.errorCode = constant::status::PathDoesNotExist;
return;
}
auto file = backing->OpenFile(path, mode);
if (file == nullptr)
response.errorCode = constant::status::GenericError;
else
manager.RegisterService(std::make_shared<IFile>(file, state, manager), session, response);
}
void IFileSystem::Commit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
}

View File

@ -3,26 +3,39 @@
#pragma once
#include <vfs/filesystem.h>
#include <services/base_service.h>
#include <services/serviceman.h>
namespace skyline::service::fssrv {
/**
* @brief These are the possible types of the filesystem
*/
enum class FsType {
Nand, //!< The internal NAND storage
SdCard, //!< The external SDCard storage
GameCard, //!< The Game-Card of the inserted game (https://switchbrew.org/wiki/Gamecard)
};
/**
* @brief IFileSystem is used to interact with a filesystem (https://switchbrew.org/wiki/Filesystem_services#IFileSystem)
*/
class IFileSystem : public BaseService {
public:
FsType type; //!< The type of filesystem this class represents
private:
std::shared_ptr<vfs::FileSystem> backing;
IFileSystem(FsType type, const DeviceState &state, ServiceManager &manager);
public:
IFileSystem(std::shared_ptr<vfs::FileSystem> backing, const DeviceState &state, ServiceManager &manager);
/**
* @brief This creates a file at the specified path in the filesystem
*/
void CreateFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This queries the DirectoryEntryType of the given path (https://switchbrew.org/wiki/Filesystem_services#GetEntryType)
*/
void GetEntryType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns an IFile handle for the requested path (https://switchbrew.org/wiki/Filesystem_services#OpenFile)
*/
void OpenFile(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This commits all changes to the filesystem (https://switchbrew.org/wiki/Filesystem_services#Commit)
*/
void Commit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
}

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <os.h>
#include <vfs/os_filesystem.h>
#include <loader/loader.h>
#include "IFileSystemProxy.h"
#include "IStorage.h"
@ -9,7 +11,9 @@ namespace skyline::service::fssrv {
IFileSystemProxy::IFileSystemProxy(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, Service::fssrv_IFileSystemProxy, "fssrv:IFileSystemProxy", {
{0x1, SFUNC(IFileSystemProxy::SetCurrentProcess)},
{0x12, SFUNC(IFileSystemProxy::OpenSdCardFileSystem)},
{0xc8, SFUNC(IFileSystemProxy::OpenDataStorageByCurrentProcess)}
{0x33, SFUNC(IFileSystemProxy::OpenSaveDataFileSystem)},
{0xc8, SFUNC(IFileSystemProxy::OpenDataStorageByCurrentProcess)},
{0x3ed, SFUNC(IFileSystemProxy::GetGlobalAccessLogMode)},
}) {}
void IFileSystemProxy::SetCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -17,7 +21,46 @@ namespace skyline::service::fssrv {
}
void IFileSystemProxy::OpenSdCardFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(std::make_shared<IFileSystem>(FsType::SdCard, state, manager), session, response);
manager.RegisterService(std::make_shared<IFileSystem>(std::make_shared<vfs::OsFileSystem>(state.os->appFilesPath + "/switch/sdmc/"), state, manager), session, response);
}
void IFileSystemProxy::OpenSaveDataFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto spaceId = request.Pop<SaveDataSpaceId>();
auto attribute = request.Pop<SaveDataAttribute>();
if (attribute.programId == 0)
attribute.programId = state.loader->nacp->nacpContents.saveDataOwnerId;
std::string saveDataPath = [spaceId, &attribute] () {
std::string spaceIdStr = [spaceId] () {
switch (spaceId) {
case SaveDataSpaceId::System:
return "/nand/system";
case SaveDataSpaceId::User:
return "/nand/user";
case SaveDataSpaceId::Temporary:
return "/nand/temp";
default:
throw exception("Unsupported savedata ID: {}", spaceId);
};
} ();
switch (attribute.type) {
case SaveDataType::System:
return fmt::format("{}/save/{:016X}/{:016X}{:016X}/", spaceIdStr, attribute.saveDataId, attribute.userId.lower, attribute.userId.upper);
case SaveDataType::Account:
case SaveDataType::Device:
return fmt::format("{}/save/{:016X}/{:016X}{:016X}/{:016X}/", spaceIdStr, 0, attribute.userId.lower, attribute.userId.upper, attribute.programId);
case SaveDataType::Temporary:
return fmt::format("{}/{:016X}/{:016X}{:016X}/{:016X}/", spaceIdStr, 0, attribute.userId.lower, attribute.userId.upper, attribute.programId);
case SaveDataType::Cache:
return fmt::format("{}/save/cache/{:016X}/", spaceIdStr, attribute.programId);
default:
throw exception("Unsupported savedata type: {}", attribute.type);
};
} ();
manager.RegisterService(std::make_shared<IFileSystem>(std::make_shared<vfs::OsFileSystem>(state.os->appFilesPath + "/switch" + saveDataPath), state, manager), session, response);
}
void IFileSystemProxy::OpenDataStorageByCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -26,4 +69,8 @@ namespace skyline::service::fssrv {
else
throw exception("Tried to call OpenDataStorageByCurrentProcess without a valid RomFS");
}
void IFileSystemProxy::GetGlobalAccessLogMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push<u32>(0);
}
}

View File

@ -5,9 +5,57 @@
#include <services/base_service.h>
#include <services/serviceman.h>
#include <services/account/IAccountServiceForApplication.h>
#include "IFileSystem.h"
namespace skyline::service::fssrv {
/**
* @brief This enumerates the possible locations savedata can be stored in
*/
enum class SaveDataSpaceId : u64 {
System = 0, //!< Savedata should be stored in the EMMC system folder
User = 1, //!< Savedata should be stored in the EMMC user folder
SdSystem = 2, //!< Savedata should be stored in the SDCard system folder
Temporary = 3, //!< Savedata should be stored in a temporary folder
SdCache = 4, //!< Savedata should be stored in the SDCard system folder
ProperSystem = 100, //!< Savedata should be stored in the system partition
};
/**
* @brief This enumerates the types of savedata
*/
enum class SaveDataType : u8 {
System = 0, //!< This is system savedata
Account = 1, //!< This is user game savedata
Bcat = 2, //!< This is user bcat savedata
Device = 3, //!< This is device-wide savedata
Temporary = 4, //!< This is temporary savedata
Cache = 5, //!< This is cache savedata
SystemBcat = 6, //!< This is device-wide bcat savedata
};
/**
* @brief This enumerates the ranks of savedata
*/
enum class SaveDataRank : u8 {
Primary, //!< This is primary savedata
Secondary, //!< This is secondary savedata
};
/**
* @brief This stores the attributes of a savedata entry
*/
struct SaveDataAttribute {
u64 programId; //!< The program ID to store the savedata contents under
account::UserId userId; //!< The user ID of whom the applications savedata contents should be stored under
u64 saveDataId; //!< The ID of the savedata
SaveDataType type; //!< The type of savedata
SaveDataRank rank; //!< The rank of the savedata
u16 index; //!< The index of the savedata
u8 _pad_[0x1a];
};
static_assert(sizeof(SaveDataAttribute) == 0x40);
/**
* @brief IFileSystemProxy or fsp-srv is responsible for providing handles to file systems (https://switchbrew.org/wiki/Filesystem_services#fsp-srv)
*/
@ -27,9 +75,19 @@ namespace skyline::service::fssrv {
*/
void OpenSdCardFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns a handle to an instance of #IFileSystem (https://switchbrew.org/wiki/Filesystem_services#IFileSystem) for the requested save data area
*/
void OpenSaveDataFileSystem(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns a handle to an instance of #IStorage (https://switchbrew.org/wiki/Filesystem_services#IStorage) for the application's data storage
*/
void OpenDataStorageByCurrentProcess(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This returns the filesystem log access mode (https://switchbrew.org/wiki/Filesystem_services#GetGlobalAccessLogMode)
*/
void GetGlobalAccessLogMode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};
}

View File

@ -11,8 +11,20 @@ namespace skyline::service::fssrv {
}) {}
void IStorage::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto offset = request.Pop<u64>();
auto size = request.Pop<u64>();
auto offset = request.Pop<i64>();
auto size = request.Pop<i64>();
if (offset < 0) {
state.logger->Warn("Trying to read a file with a negative offset");
response.errorCode = constant::status::InvAddress;
return;
}
if (size < 0) {
state.logger->Warn("Trying to read a file with a negative size");
response.errorCode = constant::status::InvSize;
return;
}
backing->Read(state.process->GetPointer<u8>(request.outputBuf.at(0).address), offset, size);
}

View File

@ -61,5 +61,37 @@ namespace skyline::vfs {
inline size_t Read(T *output, size_t offset = 0, size_t size = 0) {
return Read(reinterpret_cast<u8 *>(output), offset, size ? size : sizeof(T));
}
/**
* @brief Writes from a buffer to a particular offset in the backing
* @param input The object to write to the backing
* @param offset The offset where the input buffer should be written
* @param size The amount to write
* @return The amount of bytes written
*/
virtual size_t Write(u8 *input, size_t offset, size_t size) {
throw exception("This backing does not support being written to");
}
/**
* @brief Writes from a buffer to a particular offset in the backing (template version)
* @tparam T The type of object to write
* @param input The object to write to the backing
* @param offset The offset where the input buffer should be written
* @param size The amount to write
* @return The amount of bytes written
*/
template<typename T>
inline size_t Write(T *output, size_t offset = 0, size_t size = 0) {
return Write(reinterpret_cast<u8 *>(output), offset, size ? size : sizeof(T));
}
/**
* @brief Resizes a backing to the given size
* @param size The new size for the backing
*/
virtual void Resize(size_t size) {
throw exception("This backing does not support being resized");
}
};
}

View File

@ -22,6 +22,26 @@ namespace skyline::vfs {
virtual ~FileSystem() = default;
/**
* @brief Creates a file in the filesystem with the requested size
* @param path The path where the file should be created
* @param size The size of the file to create
* @return Whether creating the file succeeded
*/
virtual bool CreateFile(std::string path, size_t size) {
throw exception("This filesystem does not support creating files");
};
/**
* @brief Creates a directory in the filesystem
* @param path The path to where the directory should be created
* @param parents Whether all parent directories in the given path should be created
* @return Whether creating the directory succeeded
*/
virtual bool CreateDirectory(std::string path, bool parents) {
throw exception("This filesystem does not support creating directories");
};
/**
* @brief Opens a file from the specified path in the filesystem
* @param path The path to the file
@ -30,12 +50,32 @@ namespace skyline::vfs {
*/
virtual std::shared_ptr<Backing> OpenFile(std::string path, Backing::Mode mode = {true, false, false}) = 0;
/**
* @brief Queries the type of the entry given by path
* @param path The path to the entry
* @return The type of the entry, if present
*/
virtual std::optional<Directory::EntryType> GetEntryType(std::string path) = 0;
/**
* @brief Checks if a given file exists in a filesystem
* @param path The path to the file
* @return A boolean containing whether the file exists
* @return Whether the file exists
*/
virtual bool FileExists(std::string path) = 0;
inline bool FileExists(std::string path) {
auto entry = GetEntryType(path);
return entry.has_value() && entry.value() == Directory::EntryType::File;
}
/**
* @brief Checks if a given directory exists in a filesystem
* @param path The path to the directory
* @return Whether the directory exists
*/
inline bool DirectoryExists(std::string path) {
auto entry = GetEntryType(path);
return entry.has_value() && entry.value() == Directory::EntryType::Directory;
}
/**
* @brief Opens a directory from the specified path in the filesystem
@ -43,6 +83,8 @@ namespace skyline::vfs {
* @param listMode The list mode for the directory
* @return A shared pointer to a Directory object of the directory
*/
virtual std::shared_ptr<Directory> OpenDirectory(std::string path, Directory::ListMode listMode) = 0;
virtual std::shared_ptr<Directory> OpenDirectory(std::string path, Directory::ListMode listMode) {
throw exception("This filesystem does not support opening directories");
};
};
}

View File

@ -22,16 +22,18 @@ namespace skyline::vfs {
};
static_assert(sizeof(ApplicationTitle) == 0x300);
public:
/**
* @brief This struct encapsulates all the data within an NACP file
*/
struct NacpData {
std::array<ApplicationTitle, 0x10> titleEntries; //!< Title entries for each language
u8 _pad_[0x4000 - (0x10 * 0x300)];
u8 _pad0_[0x78];
u64 saveDataOwnerId; //!< The ID that should be used for this application's savedata
u8 _pad1_[0xf80];
} nacpContents{};
static_assert(sizeof(NacpData) == 0x4000);
public:
/**
* @param backing The backing for the NACP
*/

View File

@ -7,24 +7,46 @@
#include "os_backing.h"
namespace skyline::vfs {
OsBacking::OsBacking(int fd) : Backing(), fd(fd) {
OsBacking::OsBacking(int fd, bool closable, Mode mode) : Backing(mode), fd(fd), closable(closable) {
struct stat fileInfo;
if (fstat(fd, &fileInfo))
throw exception("Failed to stat fd: {}", strerror(errno));
size = fileInfo.st_size;
}
OsBacking::~OsBacking() {
if (closable)
close(fd);
}
size_t OsBacking::Read(u8 *output, size_t offset, size_t size) {
if (!mode.read)
throw exception("Attempting to read a backing that is not readable");
auto ret = pread64(fd, output, size, offset);
if (ret < 0)
throw exception("Failed to read from fd: {}", strerror(errno));
return static_cast<size_t>(ret);
}
size_t OsBacking::Write(u8 *output, size_t offset, size_t size) {
if (!mode.write)
throw exception("Attempting to write to a backing that is not writable");
auto ret = pwrite64(fd, output, size, offset);
if (ret < 0)
throw exception("Failed to write to fd: {}", strerror(errno));
return static_cast<size_t>(ret);
}
void OsBacking::Resize(size_t size) {
int ret = ftruncate(fd, size);
if (ret < 0)
throw exception("Failed to resize file: {}", strerror(errno));
this->size = size;
}
}

View File

@ -12,13 +12,20 @@ namespace skyline::vfs {
class OsBacking : public Backing {
private:
int fd; //!< An FD to the backing
bool closable; //!< Whether the FD can be closed when the backing is destroyed
public:
/**
* @param fd The file descriptor of the backing
*/
OsBacking(int fd);
OsBacking(int fd, bool closable = false, Mode = {true, false, false});
~OsBacking();
size_t Read(u8 *output, size_t offset, size_t size);
size_t Write(u8 *output, size_t offset, size_t size);
void Resize(size_t size);
};
}

View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include "os_backing.h"
#include "os_filesystem.h"
namespace skyline::vfs {
OsFileSystem::OsFileSystem(std::string basePath) : FileSystem(), basePath(basePath) {
if (!DirectoryExists(basePath))
if (!CreateDirectory(basePath, true))
throw exception("Error creating the OS filesystem backing directory");
}
bool OsFileSystem::CreateFile(std::string path, size_t size) {
auto fullPath = basePath + path;
// Create a directory that will hold the file
CreateDirectory(fullPath.substr(0, fullPath.find_last_of('/')), true);
int fd = open(fullPath.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0) {
if (errno != ENOENT)
throw exception("Failed to create file: {}", strerror(errno));
else
return false;
}
// Truncate the file to desired length
int ret = ftruncate(fd, size);
close(fd);
if (ret < 0)
throw exception("Failed to resize created file: {}", strerror(errno));
return true;
}
bool OsFileSystem::CreateDirectory(std::string path, bool parents) {
if (!parents) {
int ret = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
return ret == 0 || errno == EEXIST;
}
for (auto dir = basePath.begin(); dir != basePath.end(); dir++) {
auto nextDir = std::find(dir, basePath.end(), '/');
auto nextPath = "/" + std::string(basePath.begin(), nextDir);
int ret = mkdir(nextPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (ret < 0 && errno != EEXIST && errno != EPERM)
return false;
dir = nextDir;
}
return true;
}
std::shared_ptr<Backing> OsFileSystem::OpenFile(std::string path, Backing::Mode mode) {
if (!(mode.read || mode.write))
throw exception("Cannot open a file that is neither readable or writable");
int fd = open((basePath + path).c_str(), (mode.read && mode.write) ? O_RDWR : (mode.write ? O_WRONLY : O_RDONLY));
if (fd < 0)
throw exception("Failed to open file: {}", strerror(errno));
return std::make_shared<OsBacking>(fd, true, mode);
}
std::optional<Directory::EntryType> OsFileSystem::GetEntryType(std::string path) {
auto fullPath = basePath + path;
auto directory = opendir(fullPath.c_str());
if (directory) {
closedir(directory);
return Directory::EntryType::Directory;
}
if (access(fullPath.c_str(), F_OK) != -1)
return Directory::EntryType::File;
return std::nullopt;
}
}

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "filesystem.h"
namespace skyline::vfs {
/**
* @brief The OsFileSystem class abstracts an OS folder with the vfs::FileSystem api
*/
class OsFileSystem : public FileSystem {
private:
std::string basePath; //!< The base path for filesystem operations
public:
OsFileSystem(std::string basePath);
bool CreateFile(std::string path, size_t size);
bool CreateDirectory(std::string path, bool parents);
std::shared_ptr<Backing> OpenFile(std::string path, Backing::Mode mode = {true, false, false});
std::optional<Directory::EntryType> GetEntryType(std::string path);
};
}

View File

@ -40,8 +40,11 @@ namespace skyline::vfs {
}
}
bool PartitionFileSystem::FileExists(std::string path) {
return fileMap.count(path);
std::optional<Directory::EntryType> PartitionFileSystem::GetEntryType(std::string path) {
if (fileMap.count(path))
return Directory::EntryType::File;
return std::nullopt;
}
std::shared_ptr<Directory> PartitionFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) {

View File

@ -54,7 +54,7 @@ namespace skyline::vfs {
std::shared_ptr<Backing> OpenFile(std::string path, Backing::Mode mode = {true, false, false});
bool FileExists(std::string path);
std::optional<Directory::EntryType> GetEntryType(std::string path);
std::shared_ptr<Directory> OpenDirectory(std::string path, Directory::ListMode listMode);
};

View File

@ -5,7 +5,7 @@
#include "rom_filesystem.h"
namespace skyline::vfs {
RomFileSystem::RomFileSystem(std::shared_ptr <Backing> backing) : FileSystem(), backing(backing) {
RomFileSystem::RomFileSystem(std::shared_ptr<Backing> backing) : FileSystem(), backing(backing) {
backing->Read(&header);
TraverseDirectory(0, "");
@ -52,7 +52,7 @@ namespace skyline::vfs {
TraverseDirectory(entry.siblingOffset, path);
}
std::shared_ptr <Backing> RomFileSystem::OpenFile(std::string path, Backing::Mode mode) {
std::shared_ptr<Backing> RomFileSystem::OpenFile(std::string path, Backing::Mode mode) {
try {
const auto &entry = fileMap.at(path);
return std::make_shared<RegionBacking>(backing, header.dataOffset + entry.offset, entry.size, mode);
@ -61,11 +61,16 @@ namespace skyline::vfs {
}
}
bool RomFileSystem::FileExists(std::string path) {
return fileMap.count(path);
std::optional<Directory::EntryType> RomFileSystem::GetEntryType(std::string path) {
if (fileMap.count(path))
return Directory::EntryType::File;
else if (directoryMap.count(path))
return Directory::EntryType::Directory;
return std::nullopt;
}
std::shared_ptr <Directory> RomFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) {
std::shared_ptr<Directory> RomFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) {
try {
auto &entry = directoryMap.at(path);
return std::make_shared<RomFileSystemDirectory>(backing, header, entry, listMode);
@ -74,10 +79,10 @@ namespace skyline::vfs {
}
}
RomFileSystemDirectory::RomFileSystemDirectory(const std::shared_ptr <Backing> &backing, const RomFileSystem::RomFsHeader &header, const RomFileSystem::RomFsDirectoryEntry &ownEntry, ListMode listMode) : Directory(listMode), backing(backing), header(header), ownEntry(ownEntry) {}
RomFileSystemDirectory::RomFileSystemDirectory(const std::shared_ptr<Backing> &backing, const RomFileSystem::RomFsHeader &header, const RomFileSystem::RomFsDirectoryEntry &ownEntry, ListMode listMode) : Directory(listMode), backing(backing), header(header), ownEntry(ownEntry) {}
std::vector <RomFileSystemDirectory::Entry> RomFileSystemDirectory::Read() {
std::vector <Entry> contents;
std::vector<RomFileSystemDirectory::Entry> RomFileSystemDirectory::Read() {
std::vector<Entry> contents;
if (listMode.file) {
RomFileSystem::RomFsFileEntry romFsFileEntry;

View File

@ -81,7 +81,7 @@ namespace skyline {
std::shared_ptr<Backing> OpenFile(std::string path, Backing::Mode mode = {true, false, false});
bool FileExists(std::string path);
std::optional<Directory::EntryType> GetEntryType(std::string path);
std::shared_ptr<Directory> OpenDirectory(std::string path, Directory::ListMode listMode);
};

View File

@ -34,11 +34,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
*/
private lateinit var preferenceFd : ParcelFileDescriptor
/**
* The file descriptor of the Log file
*/
private lateinit var logFd : ParcelFileDescriptor
/**
* The surface object used for displaying frames
*/
@ -61,9 +56,9 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
* @param romType The type of the ROM as an enum value
* @param romFd The file descriptor of the ROM object
* @param preferenceFd The file descriptor of the Preference XML
* @param logFd The file descriptor of the Log file
* @param appFilesPath The full path to the app files directory
*/
private external fun executeApplication(romUri : String, romType : Int, romFd : Int, preferenceFd : Int, logFd : Int)
private external fun executeApplication(romUri : String, romType : Int, romFd : Int, preferenceFd : Int, appFilesPath : String)
/**
* This sets the halt flag in libskyline to the provided value, if set to true it causes libskyline to halt emulation
@ -90,7 +85,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
private external fun getFrametime() : Float
/**
* This executes the specified ROM, [preferenceFd] and [logFd] are assumed to be valid beforehand
* This executes the specified ROM, [preferenceFd] is assumed to be valid beforehand
*
* @param rom The URI of the ROM to execute
*/
@ -102,7 +97,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
while ((surface == null))
Thread.yield()
executeApplication(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, logFd.fd)
executeApplication(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, applicationContext.filesDir.canonicalPath + "/")
if (shouldFinish)
runOnUiThread { finish() }
@ -112,7 +107,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
}
/**
* This makes the window fullscreen then sets up [preferenceFd] and [logFd], sets up the performance statistics and finally calls [executeApplication] for executing the application
* This makes the window fullscreen then sets up [preferenceFd], sets up the performance statistics and finally calls [executeApplication] for executing the application
*/
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState : Bundle?) {
@ -136,9 +131,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
val log = File("${applicationInfo.dataDir}/skyline.log")
logFd = ParcelFileDescriptor.open(log, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_READ_WRITE)
game_view.holder.addCallback(this)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
@ -186,7 +178,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
romFd.close()
preferenceFd.close()
logFd.close()
super.onDestroy()
}

View File

@ -63,7 +63,7 @@ class LogActivity : AppCompatActivity() {
log_list.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
try {
logFile = File("${applicationInfo.dataDir}/skyline.log")
logFile = File(applicationContext.filesDir.canonicalPath + "/skyline.log")
logFile.forEachLine {
adapter.add(it)

View File

@ -93,7 +93,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
private fun refreshAdapter(tryLoad : Boolean) {
if (tryLoad) {
try {
adapter.load(File("${applicationInfo.dataDir}/roms.bin"))
adapter.load(File(applicationContext.filesDir.canonicalPath + "/roms.bin"))
return
} catch (e : Exception) {
Log.w("refreshFiles", "Ran into exception while loading: ${e.message}")
@ -121,7 +121,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
if (!foundRoms) adapter.addHeader(getString(R.string.no_rom))
try {
adapter.save(File("${applicationInfo.dataDir}/roms.bin"))
adapter.save(File(applicationContext.filesDir.canonicalPath + "/roms.bin"))
} catch (e : IOException) {
Log.w("refreshFiles", "Ran into exception while saving: ${e.message}")
}