diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index bc88b3b1..b4c29c98 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -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 diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 971025d2..84b9b07c 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -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(env, instance); auto settings = std::make_shared(preferenceFd); - auto logger = std::make_shared(logFd, static_cast(std::stoi(settings->GetString("log_level")))); + + auto appFilesPath = env->GetStringUTFChars(appFilesPathJstring, nullptr); + auto logger = std::make_shared(std::string(appFilesPath) + "skyline.log", static_cast(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(frametime) / 100; -} \ No newline at end of file +} diff --git a/app/src/main/cpp/skyline/common.cpp b/app/src/main/cpp/skyline/common.cpp index 0b6cd364..461cf417 100644 --- a/app/src/main/cpp/skyline/common.cpp +++ b/app/src/main/cpp/skyline/common.cpp @@ -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"); } diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 68ff61f3..b4b7e809 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -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 diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index bf23f6ac..c17d2935 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -10,7 +10,7 @@ #include "os.h" namespace skyline::kernel { - OS::OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings) : state(this, process, jvmManager, settings, logger), memory(state), serviceManager(state) {} + OS::OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &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(romFd); diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 7983c660..9f937864 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -25,13 +25,14 @@ namespace skyline::kernel { std::shared_ptr 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, std::shared_ptr &logger, std::shared_ptr &settings); + OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings, const std::string &appFilesPath); /** * @brief Execute a particular ROM file. This launches the main process and calls the NCE class to handle execution. diff --git a/app/src/main/cpp/skyline/services/base_service.h b/app/src/main/cpp/skyline/services/base_service.h index 861a7fa4..dd354a37 100644 --- a/app/src/main/cpp/skyline/services/base_service.h +++ b/app/src/main/cpp/skyline/services/base_service.h @@ -58,6 +58,7 @@ namespace skyline::service { timesrv_ISteadyClock, fssrv_IFileSystemProxy, fssrv_IFileSystem, + fssrv_IFile, fssrv_IStorage, nvdrv_INvDrvServices, visrv_IManagerRootService, diff --git a/app/src/main/cpp/skyline/services/fssrv/IFile.cpp b/app/src/main/cpp/skyline/services/fssrv/IFile.cpp new file mode 100644 index 00000000..920ed54b --- /dev/null +++ b/app/src/main/cpp/skyline/services/fssrv/IFile.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include "IFile.h" + +namespace skyline::service::fssrv { + IFile::IFile(std::shared_ptr &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(); + request.Skip(); + auto offset = request.Pop(); + auto size = request.Pop(); + + 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(static_cast(backing->Read(state.process->GetPointer(request.outputBuf.at(0).address), offset, size))); + } + + void IFile::Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto writeOption = request.Pop(); + request.Skip(); + auto offset = request.Pop(); + auto size = request.Pop(); + + 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(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()); + } + + void IFile::GetSize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + response.Push(backing->size); + } +} diff --git a/app/src/main/cpp/skyline/services/fssrv/IFile.h b/app/src/main/cpp/skyline/services/fssrv/IFile.h new file mode 100644 index 00000000..fc872041 --- /dev/null +++ b/app/src/main/cpp/skyline/services/fssrv/IFile.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include + +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 backing; //!< The backing of the IFile + + public: + IFile(std::shared_ptr &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); + }; +} diff --git a/app/src/main/cpp/skyline/services/fssrv/IFileSystem.cpp b/app/src/main/cpp/skyline/services/fssrv/IFileSystem.cpp index 63fdd30e..8a8e9641 100644 --- a/app/src/main/cpp/skyline/services/fssrv/IFileSystem.cpp +++ b/app/src/main/cpp/skyline/services/fssrv/IFileSystem.cpp @@ -1,8 +1,55 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include +#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 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(request.inputBuf.at(0).address)); + auto mode = request.Pop(); + auto size = request.Pop(); + + 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(request.inputBuf.at(0).address)); + + auto type = backing->GetEntryType(path); + + if (type.has_value()) { + response.Push(type.value()); + } else { + response.Push(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(request.inputBuf.at(0).address)); + auto mode = request.Pop(); + + 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(file, state, manager), session, response); + } + + void IFileSystem::Commit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} } diff --git a/app/src/main/cpp/skyline/services/fssrv/IFileSystem.h b/app/src/main/cpp/skyline/services/fssrv/IFileSystem.h index 34f64683..8ed971b4 100644 --- a/app/src/main/cpp/skyline/services/fssrv/IFileSystem.h +++ b/app/src/main/cpp/skyline/services/fssrv/IFileSystem.h @@ -3,26 +3,39 @@ #pragma once +#include #include #include 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 backing; - IFileSystem(FsType type, const DeviceState &state, ServiceManager &manager); + public: + IFileSystem(std::shared_ptr 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); }; } diff --git a/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.cpp b/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.cpp index 4221b64f..22a9f100 100644 --- a/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.cpp +++ b/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include #include #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(FsType::SdCard, state, manager), session, response); + manager.RegisterService(std::make_shared(std::make_shared(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(); + auto attribute = request.Pop(); + + 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(std::make_shared(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(0); + } } diff --git a/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.h b/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.h index ecd4beed..6985ae26 100644 --- a/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.h +++ b/app/src/main/cpp/skyline/services/fssrv/IFileSystemProxy.h @@ -5,9 +5,57 @@ #include #include +#include #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); }; } diff --git a/app/src/main/cpp/skyline/services/fssrv/IStorage.cpp b/app/src/main/cpp/skyline/services/fssrv/IStorage.cpp index a0968a9b..dae528cb 100644 --- a/app/src/main/cpp/skyline/services/fssrv/IStorage.cpp +++ b/app/src/main/cpp/skyline/services/fssrv/IStorage.cpp @@ -11,8 +11,20 @@ namespace skyline::service::fssrv { }) {} void IStorage::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto offset = request.Pop(); - auto size = request.Pop(); + auto offset = request.Pop(); + auto size = request.Pop(); + + 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(request.outputBuf.at(0).address), offset, size); } diff --git a/app/src/main/cpp/skyline/vfs/backing.h b/app/src/main/cpp/skyline/vfs/backing.h index d0f76b9a..873098ee 100644 --- a/app/src/main/cpp/skyline/vfs/backing.h +++ b/app/src/main/cpp/skyline/vfs/backing.h @@ -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(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 + inline size_t Write(T *output, size_t offset = 0, size_t size = 0) { + return Write(reinterpret_cast(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"); + } }; } diff --git a/app/src/main/cpp/skyline/vfs/filesystem.h b/app/src/main/cpp/skyline/vfs/filesystem.h index b7240d5d..ce47923f 100644 --- a/app/src/main/cpp/skyline/vfs/filesystem.h +++ b/app/src/main/cpp/skyline/vfs/filesystem.h @@ -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 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 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 OpenDirectory(std::string path, Directory::ListMode listMode) = 0; + virtual std::shared_ptr OpenDirectory(std::string path, Directory::ListMode listMode) { + throw exception("This filesystem does not support opening directories"); + }; }; } diff --git a/app/src/main/cpp/skyline/vfs/nacp.h b/app/src/main/cpp/skyline/vfs/nacp.h index 63b1b5a3..8a4f2cee 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.h +++ b/app/src/main/cpp/skyline/vfs/nacp.h @@ -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 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 */ diff --git a/app/src/main/cpp/skyline/vfs/os_backing.cpp b/app/src/main/cpp/skyline/vfs/os_backing.cpp index 6bd9e1af..f46192f8 100644 --- a/app/src/main/cpp/skyline/vfs/os_backing.cpp +++ b/app/src/main/cpp/skyline/vfs/os_backing.cpp @@ -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(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(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; + } } \ No newline at end of file diff --git a/app/src/main/cpp/skyline/vfs/os_backing.h b/app/src/main/cpp/skyline/vfs/os_backing.h index 7592bf82..f92a7adf 100644 --- a/app/src/main/cpp/skyline/vfs/os_backing.h +++ b/app/src/main/cpp/skyline/vfs/os_backing.h @@ -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); }; } \ No newline at end of file diff --git a/app/src/main/cpp/skyline/vfs/os_filesystem.cpp b/app/src/main/cpp/skyline/vfs/os_filesystem.cpp new file mode 100644 index 00000000..f357dd93 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/os_filesystem.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include + +#include +#include +#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 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(fd, true, mode); + } + + std::optional 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; + } +} diff --git a/app/src/main/cpp/skyline/vfs/os_filesystem.h b/app/src/main/cpp/skyline/vfs/os_filesystem.h new file mode 100644 index 00000000..015c7737 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/os_filesystem.h @@ -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 OpenFile(std::string path, Backing::Mode mode = {true, false, false}); + + std::optional GetEntryType(std::string path); + }; +} diff --git a/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp b/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp index ee1516b5..ec3e648b 100644 --- a/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp +++ b/app/src/main/cpp/skyline/vfs/partition_filesystem.cpp @@ -40,8 +40,11 @@ namespace skyline::vfs { } } - bool PartitionFileSystem::FileExists(std::string path) { - return fileMap.count(path); + std::optional PartitionFileSystem::GetEntryType(std::string path) { + if (fileMap.count(path)) + return Directory::EntryType::File; + + return std::nullopt; } std::shared_ptr PartitionFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) { diff --git a/app/src/main/cpp/skyline/vfs/partition_filesystem.h b/app/src/main/cpp/skyline/vfs/partition_filesystem.h index 86a7bb32..dd70cd1c 100644 --- a/app/src/main/cpp/skyline/vfs/partition_filesystem.h +++ b/app/src/main/cpp/skyline/vfs/partition_filesystem.h @@ -54,7 +54,7 @@ namespace skyline::vfs { std::shared_ptr OpenFile(std::string path, Backing::Mode mode = {true, false, false}); - bool FileExists(std::string path); + std::optional GetEntryType(std::string path); std::shared_ptr OpenDirectory(std::string path, Directory::ListMode listMode); }; diff --git a/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp b/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp index 35b646cd..2e2f6ed1 100644 --- a/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp +++ b/app/src/main/cpp/skyline/vfs/rom_filesystem.cpp @@ -5,7 +5,7 @@ #include "rom_filesystem.h" namespace skyline::vfs { - RomFileSystem::RomFileSystem(std::shared_ptr backing) : FileSystem(), backing(backing) { + RomFileSystem::RomFileSystem(std::shared_ptr backing) : FileSystem(), backing(backing) { backing->Read(&header); TraverseDirectory(0, ""); @@ -52,7 +52,7 @@ namespace skyline::vfs { TraverseDirectory(entry.siblingOffset, path); } - std::shared_ptr RomFileSystem::OpenFile(std::string path, Backing::Mode mode) { + std::shared_ptr RomFileSystem::OpenFile(std::string path, Backing::Mode mode) { try { const auto &entry = fileMap.at(path); return std::make_shared(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 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 RomFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) { + std::shared_ptr RomFileSystem::OpenDirectory(std::string path, Directory::ListMode listMode) { try { auto &entry = directoryMap.at(path); return std::make_shared(backing, header, entry, listMode); @@ -74,10 +79,10 @@ namespace skyline::vfs { } } - RomFileSystemDirectory::RomFileSystemDirectory(const std::shared_ptr &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, const RomFileSystem::RomFsHeader &header, const RomFileSystem::RomFsDirectoryEntry &ownEntry, ListMode listMode) : Directory(listMode), backing(backing), header(header), ownEntry(ownEntry) {} - std::vector RomFileSystemDirectory::Read() { - std::vector contents; + std::vector RomFileSystemDirectory::Read() { + std::vector contents; if (listMode.file) { RomFileSystem::RomFsFileEntry romFsFileEntry; diff --git a/app/src/main/cpp/skyline/vfs/rom_filesystem.h b/app/src/main/cpp/skyline/vfs/rom_filesystem.h index edfcff97..6e3cf198 100644 --- a/app/src/main/cpp/skyline/vfs/rom_filesystem.h +++ b/app/src/main/cpp/skyline/vfs/rom_filesystem.h @@ -81,7 +81,7 @@ namespace skyline { std::shared_ptr OpenFile(std::string path, Backing::Mode mode = {true, false, false}); - bool FileExists(std::string path); + std::optional GetEntryType(std::string path); std::shared_ptr OpenDirectory(std::string path, Directory::ListMode listMode); }; diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index c2820b75..d4c03f47 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -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() } diff --git a/app/src/main/java/emu/skyline/LogActivity.kt b/app/src/main/java/emu/skyline/LogActivity.kt index eab05cc5..48f4bdd4 100644 --- a/app/src/main/java/emu/skyline/LogActivity.kt +++ b/app/src/main/java/emu/skyline/LogActivity.kt @@ -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) diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index 6d8ef729..cf7dc347 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -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}") }