mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-05-28 05:34:11 -04:00
Implement the NCM sysmodule (closes #91)
* Implement NCM * Modernize ncm_main * Remove unnecessary smExit * Give access to svcCallSecureMonitor * Stack size bump * Fix incorrect setup for NandUser's content storage entry * Fix a potential data abort when flushing the placeholder accessor cache * Fix HasFile and HasDirectory * Use r+b, not w+b * Misc fixes * errno begone * Fixed more stdio error handling * More main fixes * Various command improvements * Make dispatch tables great again * Fix logic inversion * Fixed content path generation * Bump heap size, fix CleanupAllPlaceHolder * Various fixes. Note: This contains debug stuff which will be removed later. I was getting tired of having to cherrypick tiny changes * Fixed placeholder/content deletion * Fixed incorrect content manager destruction * Prevent automatic placeholder creation on open * Fixed List implementation. Also lots of debug logging. * Removed debug code * Added a scope guard for WritePlaceHolder * Manually prevent placeholder/content appending * Revert "Removed debug code" This reverts commitd6ff261fcc
. * Always cache placeholder file. Switch to ftell for preventing appending * Universally use EnsureEnabled * Abstract away file writing logic * Misc cleanup * Refactor placeholder cacheing * Remove debug code (again) * Revert "Remove debug code (again)" This reverts commit168447d80e
. * Misc changes * Fixed file modes * Fixed ContentId/PlaceHolderId alignment * Improved type safety * Fixed reinitialization * Fixed doubleup on path creation * Remove debug code * Fixed 1.0.0 booting * Correct amount of add on content * Correct main thread stack size * lr: Introducing registered data * Reorder stratosphere Makefile * Move results to libstrat * lr: Cleanup lr_redirection * lr: lr_manager tweaks * lr: Imrpoved path handling and adjust ResolveAddOnContentPath order * lr: Organise types * Add eof newlines * lr: Eliminate unnecessary vars * lr: Unnecessary vars 2 electric boogaloo * lr: Various helpers * lr: RegisteredLocationResolver helpers * ncm: Move ncm_types to libstrat * ncm: Misc cleanup * Implement NCM * Modernize ncm_main * Remove unnecessary smExit * Give access to svcCallSecureMonitor * Stack size bump * Fix incorrect setup for NandUser's content storage entry * Fix a potential data abort when flushing the placeholder accessor cache * Fix HasFile and HasDirectory * Use r+b, not w+b * Misc fixes * errno begone * Fixed more stdio error handling * More main fixes * Various command improvements * Make dispatch tables great again * Fix logic inversion * Fixed content path generation * Bump heap size, fix CleanupAllPlaceHolder * Various fixes. Note: This contains debug stuff which will be removed later. I was getting tired of having to cherrypick tiny changes * Fixed placeholder/content deletion * Fixed incorrect content manager destruction * Prevent automatic placeholder creation on open * Fixed List implementation. Also lots of debug logging. * Removed debug code * Added a scope guard for WritePlaceHolder * Manually prevent placeholder/content appending * Revert "Removed debug code" This reverts commitd6ff261fcc
. * Always cache placeholder file. Switch to ftell for preventing appending * Universally use EnsureEnabled * Abstract away file writing logic * Misc cleanup * Refactor placeholder cacheing * Remove debug code (again) * Revert "Remove debug code (again)" This reverts commit168447d80e
. * Misc changes * Fixed file modes * Fixed ContentId/PlaceHolderId alignment * Improved type safety * Fixed reinitialization * Fixed doubleup on path creation * Remove debug code * Fixed 1.0.0 booting * Correct amount of add on content * Correct main thread stack size * lr: Introducing registered data * Reorder stratosphere Makefile * Move results to libstrat * lr: Cleanup lr_redirection * lr: lr_manager tweaks * lr: Imrpoved path handling and adjust ResolveAddOnContentPath order * lr: Organise types * Add eof newlines * lr: Eliminate unnecessary vars * lr: Unnecessary vars 2 electric boogaloo * lr: Various helpers * lr: RegisteredLocationResolver helpers * ncm: Move ncm_types to libstrat * ncm: Misc cleanup * Updated AddOnContentLocationResolver and RegisteredLocationResolver to 9.0.0 * Finished updating lr to 9.0.0 * Updated NCM to 9.0.0 * Fix libstrat includes * Fixed application launching * title_id_2 -> owner_tid * Updated to new-ipc * Change to using pure virtuals * Title Id -> Program Id * Fixed compilation against master * std::scoped_lock<> -> std::scoped_lock * Adopted R_UNLESS and R_CONVERT * Prefix namespace to Results * Adopt std::numeric_limits * Fixed incorrect error handling in ReadFile * Adopted AMS_ABORT_UNLESS * Adopt util::GenerateUuid() * Syntax improvements * ncm_types: Address review * Address more review comments * Updated copyrights * Address more feedback * More feedback addressed * More changes * Move dispatch tables out of interface files * Addressed remaining comments * lr: move into libstratosphere * ncm: Fix logic inversion * lr: Add comments * lr: Remove whitespace * ncm: Start addressing feedback * ncm: Cleanup InitializeContentManager * lr: support client-side usage * lr_service -> lr_api * ncm: Begin refactoring content manager * ncm: More content manager improvements * ncm: Content manager mount improvements * ldr: use lr bindings * lr bindings usage: minor fixes * ncm/lr: Pointer placement * ncm: placeholder accessor cleanup * ncm: minor fixes * ncm: refactor rights cache * ncm: content meta database cleanup * ncm: move content meta database impl out of interface file * ncm: Use const ContentMetaKey & * ncm: fix other non-const ContentMetaKey references * ncm: content meta database cleanup * ncm: content storage fixes for 2.0.0 * ncm: add missing end of file newlines * ncm: implement ContentMetaReader * ncm: client-side api * ncm: trim trailing spaces * ncm: FS_MAX_PATH-1 -> fs::EntryNameLengthMax * ncm: Use PathString and Path * fs: implement accessor wrappers for ncm * fs: implement user fs wrappers * fs: add MountSdCard * ncm: move to content manager impl * ncm: fix up main * kvdb: use fs:: * fs: Add wrappers needed for ncm * ncm: use fs bindings, other refactoring * ncm: minor fixes * fsa: fix ReadFile without size output * fs: add substorage, rom path tool * ncm: fix dangling fsdev usage * fs: fix bug in Commit * fs: fixed incorrect mode check * fs: implement Mount(System)Data * ncm: don't delete hos * results: add R_SUCCEED_IF * ams-except-ncm: use R_SUCCEED_IF * ncm: added comments * ncm: fix api definitions * ncm: use R_SUCCEED_IF * pm: think of the savings * ncm: employ kernel strats * ncm: Nintendo has 5 MiB of heap. Give ourselves 4 to be safe, pending analysis * ncm: refactor IDs, split types header into many headers * ams.mitm: use fs bindings instead of stdio * fs: SystemData uses SystemDataId * ncm: improve meta-db accuracy * ncm: inline getlatestkey * fs: improve UnsupportedOperation results * fs: modernize mount utils * ams: misc fixes for merge-errors * fs: improve unsupportedoperation results * git subrepo pull emummc subrepo: subdir: "emummc" merged: "d12dd546" upstream: origin: "https://github.com/m4xw/emuMMC" branch: "develop" commit: "d12dd546" git-subrepo: version: "0.4.1" origin: "???" commit: "???" * util: add boundedmap * ncm: minor style fixes * ncm: don't unmount if mounting fails * lr: bug fixes * ncm: implement ncm.for-initialize + ncm.for-safemode * lr: ncm::ProgramId::Invalid -> ncm::InvalidProgramId * ncm: fix open directory mode on 1.0.0 * ncm: fix fs use, implement more of < 4.0.0 for-initialize/safemode * ncm: implement packagedcontent -> content for building metadb * ncm: fix save data flag management * ncm: address some review suggestions (thanks @leoetlino!) * updater: use fs bindings * fs: implement MountCode * fs: prefer make_unique to operator new * ncm: implement remaining ContentMetaDatabaseBuilder functionality Co-authored-by: Michael Scire <SciresM@gmail.com>
This commit is contained in:
parent
f9403201f0
commit
c7026b9094
254 changed files with 16876 additions and 1274 deletions
|
@ -13,7 +13,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <dirent.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::boot2 {
|
||||
|
@ -24,99 +23,99 @@ namespace ams::boot2 {
|
|||
|
||||
/* psc, bus, pcv is the minimal set of required programs to get SD card. */
|
||||
/* bus depends on pcie, and pcv depends on settings. */
|
||||
constexpr ncm::ProgramId PreSdCardLaunchPrograms[] = {
|
||||
ncm::ProgramId::Psc, /* psc */
|
||||
ncm::ProgramId::Pcie, /* pcie */
|
||||
ncm::ProgramId::Bus, /* bus */
|
||||
ncm::ProgramId::Settings, /* settings */
|
||||
ncm::ProgramId::Pcv, /* pcv */
|
||||
ncm::ProgramId::Usb, /* usb */
|
||||
constexpr ncm::SystemProgramId PreSdCardLaunchPrograms[] = {
|
||||
ncm::SystemProgramId::Psc, /* psc */
|
||||
ncm::SystemProgramId::Pcie, /* pcie */
|
||||
ncm::SystemProgramId::Bus, /* bus */
|
||||
ncm::SystemProgramId::Settings, /* settings */
|
||||
ncm::SystemProgramId::Pcv, /* pcv */
|
||||
ncm::SystemProgramId::Usb, /* usb */
|
||||
};
|
||||
constexpr size_t NumPreSdCardLaunchPrograms = util::size(PreSdCardLaunchPrograms);
|
||||
|
||||
constexpr ncm::ProgramId AdditionalLaunchPrograms[] = {
|
||||
ncm::ProgramId::Tma, /* tma */
|
||||
ncm::ProgramId::Am, /* am */
|
||||
ncm::ProgramId::NvServices, /* nvservices */
|
||||
ncm::ProgramId::NvnFlinger, /* nvnflinger */
|
||||
ncm::ProgramId::Vi, /* vi */
|
||||
ncm::ProgramId::Ns, /* ns */
|
||||
ncm::ProgramId::LogManager, /* lm */
|
||||
ncm::ProgramId::Ppc, /* ppc */
|
||||
ncm::ProgramId::Ptm, /* ptm */
|
||||
ncm::ProgramId::Hid, /* hid */
|
||||
ncm::ProgramId::Audio, /* audio */
|
||||
ncm::ProgramId::Lbl, /* lbl */
|
||||
ncm::ProgramId::Wlan, /* wlan */
|
||||
ncm::ProgramId::Bluetooth, /* bluetooth */
|
||||
ncm::ProgramId::BsdSockets, /* bsdsockets */
|
||||
ncm::ProgramId::Nifm, /* nifm */
|
||||
ncm::ProgramId::Ldn, /* ldn */
|
||||
ncm::ProgramId::Account, /* account */
|
||||
ncm::ProgramId::Friends, /* friends */
|
||||
ncm::ProgramId::Nfc, /* nfc */
|
||||
ncm::ProgramId::JpegDec, /* jpegdec */
|
||||
ncm::ProgramId::CapSrv, /* capsrv */
|
||||
ncm::ProgramId::Ssl, /* ssl */
|
||||
ncm::ProgramId::Nim, /* nim */
|
||||
ncm::ProgramId::Bcat, /* bcat */
|
||||
ncm::ProgramId::Erpt, /* erpt */
|
||||
ncm::ProgramId::Es, /* es */
|
||||
ncm::ProgramId::Pctl, /* pctl */
|
||||
ncm::ProgramId::Btm, /* btm */
|
||||
ncm::ProgramId::Eupld, /* eupld */
|
||||
ncm::ProgramId::Glue, /* glue */
|
||||
/* ncm::ProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
|
||||
ncm::ProgramId::Npns, /* npns */
|
||||
ncm::ProgramId::Fatal, /* fatal */
|
||||
ncm::ProgramId::Ro, /* ro */
|
||||
ncm::ProgramId::Profiler, /* profiler */
|
||||
ncm::ProgramId::Sdb, /* sdb */
|
||||
ncm::ProgramId::Migration, /* migration */
|
||||
ncm::ProgramId::Grc, /* grc */
|
||||
ncm::ProgramId::Olsc, /* olsc */
|
||||
ncm::ProgramId::Ngct, /* ngct */
|
||||
constexpr ncm::SystemProgramId AdditionalLaunchPrograms[] = {
|
||||
ncm::SystemProgramId::Tma, /* tma */
|
||||
ncm::SystemProgramId::Am, /* am */
|
||||
ncm::SystemProgramId::NvServices, /* nvservices */
|
||||
ncm::SystemProgramId::NvnFlinger, /* nvnflinger */
|
||||
ncm::SystemProgramId::Vi, /* vi */
|
||||
ncm::SystemProgramId::Ns, /* ns */
|
||||
ncm::SystemProgramId::LogManager, /* lm */
|
||||
ncm::SystemProgramId::Ppc, /* ppc */
|
||||
ncm::SystemProgramId::Ptm, /* ptm */
|
||||
ncm::SystemProgramId::Hid, /* hid */
|
||||
ncm::SystemProgramId::Audio, /* audio */
|
||||
ncm::SystemProgramId::Lbl, /* lbl */
|
||||
ncm::SystemProgramId::Wlan, /* wlan */
|
||||
ncm::SystemProgramId::Bluetooth, /* bluetooth */
|
||||
ncm::SystemProgramId::BsdSockets, /* bsdsockets */
|
||||
ncm::SystemProgramId::Nifm, /* nifm */
|
||||
ncm::SystemProgramId::Ldn, /* ldn */
|
||||
ncm::SystemProgramId::Account, /* account */
|
||||
ncm::SystemProgramId::Friends, /* friends */
|
||||
ncm::SystemProgramId::Nfc, /* nfc */
|
||||
ncm::SystemProgramId::JpegDec, /* jpegdec */
|
||||
ncm::SystemProgramId::CapSrv, /* capsrv */
|
||||
ncm::SystemProgramId::Ssl, /* ssl */
|
||||
ncm::SystemProgramId::Nim, /* nim */
|
||||
ncm::SystemProgramId::Bcat, /* bcat */
|
||||
ncm::SystemProgramId::Erpt, /* erpt */
|
||||
ncm::SystemProgramId::Es, /* es */
|
||||
ncm::SystemProgramId::Pctl, /* pctl */
|
||||
ncm::SystemProgramId::Btm, /* btm */
|
||||
ncm::SystemProgramId::Eupld, /* eupld */
|
||||
ncm::SystemProgramId::Glue, /* glue */
|
||||
/* ncm::SystemProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
|
||||
ncm::SystemProgramId::Npns, /* npns */
|
||||
ncm::SystemProgramId::Fatal, /* fatal */
|
||||
ncm::SystemProgramId::Ro, /* ro */
|
||||
ncm::SystemProgramId::Profiler, /* profiler */
|
||||
ncm::SystemProgramId::Sdb, /* sdb */
|
||||
ncm::SystemProgramId::Migration, /* migration */
|
||||
ncm::SystemProgramId::Grc, /* grc */
|
||||
ncm::SystemProgramId::Olsc, /* olsc */
|
||||
ncm::SystemProgramId::Ngct, /* ngct */
|
||||
};
|
||||
constexpr size_t NumAdditionalLaunchPrograms = util::size(AdditionalLaunchPrograms);
|
||||
|
||||
constexpr ncm::ProgramId AdditionalMaintenanceLaunchPrograms[] = {
|
||||
ncm::ProgramId::Tma, /* tma */
|
||||
ncm::ProgramId::Am, /* am */
|
||||
ncm::ProgramId::NvServices, /* nvservices */
|
||||
ncm::ProgramId::NvnFlinger, /* nvnflinger */
|
||||
ncm::ProgramId::Vi, /* vi */
|
||||
ncm::ProgramId::Ns, /* ns */
|
||||
ncm::ProgramId::LogManager, /* lm */
|
||||
ncm::ProgramId::Ppc, /* ppc */
|
||||
ncm::ProgramId::Ptm, /* ptm */
|
||||
ncm::ProgramId::Hid, /* hid */
|
||||
ncm::ProgramId::Audio, /* audio */
|
||||
ncm::ProgramId::Lbl, /* lbl */
|
||||
ncm::ProgramId::Wlan, /* wlan */
|
||||
ncm::ProgramId::Bluetooth, /* bluetooth */
|
||||
ncm::ProgramId::BsdSockets, /* bsdsockets */
|
||||
ncm::ProgramId::Nifm, /* nifm */
|
||||
ncm::ProgramId::Ldn, /* ldn */
|
||||
ncm::ProgramId::Account, /* account */
|
||||
ncm::ProgramId::Nfc, /* nfc */
|
||||
ncm::ProgramId::JpegDec, /* jpegdec */
|
||||
ncm::ProgramId::CapSrv, /* capsrv */
|
||||
ncm::ProgramId::Ssl, /* ssl */
|
||||
ncm::ProgramId::Nim, /* nim */
|
||||
ncm::ProgramId::Erpt, /* erpt */
|
||||
ncm::ProgramId::Es, /* es */
|
||||
ncm::ProgramId::Pctl, /* pctl */
|
||||
ncm::ProgramId::Btm, /* btm */
|
||||
ncm::ProgramId::Glue, /* glue */
|
||||
/* ncm::ProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
|
||||
ncm::ProgramId::Fatal, /* fatal */
|
||||
ncm::ProgramId::Ro, /* ro */
|
||||
ncm::ProgramId::Profiler, /* profiler */
|
||||
ncm::ProgramId::Sdb, /* sdb */
|
||||
ncm::ProgramId::Migration, /* migration */
|
||||
ncm::ProgramId::Grc, /* grc */
|
||||
ncm::ProgramId::Olsc, /* olsc */
|
||||
ncm::ProgramId::Ngct, /* ngct */
|
||||
constexpr ncm::SystemProgramId AdditionalMaintenanceLaunchPrograms[] = {
|
||||
ncm::SystemProgramId::Tma, /* tma */
|
||||
ncm::SystemProgramId::Am, /* am */
|
||||
ncm::SystemProgramId::NvServices, /* nvservices */
|
||||
ncm::SystemProgramId::NvnFlinger, /* nvnflinger */
|
||||
ncm::SystemProgramId::Vi, /* vi */
|
||||
ncm::SystemProgramId::Ns, /* ns */
|
||||
ncm::SystemProgramId::LogManager, /* lm */
|
||||
ncm::SystemProgramId::Ppc, /* ppc */
|
||||
ncm::SystemProgramId::Ptm, /* ptm */
|
||||
ncm::SystemProgramId::Hid, /* hid */
|
||||
ncm::SystemProgramId::Audio, /* audio */
|
||||
ncm::SystemProgramId::Lbl, /* lbl */
|
||||
ncm::SystemProgramId::Wlan, /* wlan */
|
||||
ncm::SystemProgramId::Bluetooth, /* bluetooth */
|
||||
ncm::SystemProgramId::BsdSockets, /* bsdsockets */
|
||||
ncm::SystemProgramId::Nifm, /* nifm */
|
||||
ncm::SystemProgramId::Ldn, /* ldn */
|
||||
ncm::SystemProgramId::Account, /* account */
|
||||
ncm::SystemProgramId::Nfc, /* nfc */
|
||||
ncm::SystemProgramId::JpegDec, /* jpegdec */
|
||||
ncm::SystemProgramId::CapSrv, /* capsrv */
|
||||
ncm::SystemProgramId::Ssl, /* ssl */
|
||||
ncm::SystemProgramId::Nim, /* nim */
|
||||
ncm::SystemProgramId::Erpt, /* erpt */
|
||||
ncm::SystemProgramId::Es, /* es */
|
||||
ncm::SystemProgramId::Pctl, /* pctl */
|
||||
ncm::SystemProgramId::Btm, /* btm */
|
||||
ncm::SystemProgramId::Glue, /* glue */
|
||||
/* ncm::SystemProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */
|
||||
ncm::SystemProgramId::Fatal, /* fatal */
|
||||
ncm::SystemProgramId::Ro, /* ro */
|
||||
ncm::SystemProgramId::Profiler, /* profiler */
|
||||
ncm::SystemProgramId::Sdb, /* sdb */
|
||||
ncm::SystemProgramId::Migration, /* migration */
|
||||
ncm::SystemProgramId::Grc, /* grc */
|
||||
ncm::SystemProgramId::Olsc, /* olsc */
|
||||
ncm::SystemProgramId::Ngct, /* ngct */
|
||||
};
|
||||
constexpr size_t NumAdditionalMaintenanceLaunchPrograms = util::size(AdditionalMaintenanceLaunchPrograms);
|
||||
|
||||
|
@ -150,7 +149,7 @@ namespace ams::boot2 {
|
|||
}
|
||||
}
|
||||
|
||||
void LaunchList(const ncm::ProgramId *launch_list, size_t num_entries) {
|
||||
void LaunchList(const ncm::SystemProgramId *launch_list, size_t num_entries) {
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(launch_list[i], ncm::StorageId::BuiltInSystem), 0);
|
||||
}
|
||||
|
@ -191,19 +190,20 @@ namespace ams::boot2 {
|
|||
template<typename F>
|
||||
void IterateOverFlaggedProgramsOnSdCard(F f) {
|
||||
/* Validate that the contents directory exists. */
|
||||
DIR *contents_dir = opendir("sdmc:/atmosphere/contents");
|
||||
if (contents_dir == nullptr) {
|
||||
fs::DirectoryHandle contents_dir;
|
||||
if (R_FAILED(fs::OpenDirectory(&contents_dir, "sdmc:/atmosphere/contents", fs::OpenDirectoryMode_Directory))) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT { closedir(contents_dir); };
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(contents_dir); };
|
||||
|
||||
/* Iterate over entries in the contents directory */
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(contents_dir)) != nullptr) {
|
||||
fs::DirectoryEntry entry;
|
||||
s64 count;
|
||||
while (R_SUCCEEDED(fs::ReadDirectory(&count, &entry, contents_dir, 1)) && count == 1) {
|
||||
/* Check that the subdirectory can be converted to a program id. */
|
||||
if (std::strlen(ent->d_name) == 2 * sizeof(ncm::ProgramId) && IsHexadecimal(ent->d_name)) {
|
||||
if (std::strlen(entry.name) == 2 * sizeof(ncm::ProgramId) && IsHexadecimal(entry.name)) {
|
||||
/* Check if we've already launched the program. */
|
||||
ncm::ProgramId program_id{std::strtoul(ent->d_name, nullptr, 16)};
|
||||
ncm::ProgramId program_id{std::strtoul(entry.name, nullptr, 16)};
|
||||
|
||||
/* Check if the program is flagged. */
|
||||
if (!cfg::HasContentSpecificFlag(program_id, "boot2")) {
|
||||
|
@ -224,14 +224,16 @@ namespace ams::boot2 {
|
|||
|
||||
/* Read the mitm list off the SD card. */
|
||||
{
|
||||
char path[FS_MAX_PATH];
|
||||
std::snprintf(mitm_list, sizeof(mitm_list), "sdmc:/atmosphere/contents/%016lx/mitm.lst", static_cast<u64>(program_id));
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f == nullptr) {
|
||||
char path[fs::EntryNameLengthMax];
|
||||
std::snprintf(path, sizeof(path), "sdmc:/atmosphere/contents/%016lx/mitm.lst", static_cast<u64>(program_id));
|
||||
|
||||
fs::FileHandle f;
|
||||
if (R_FAILED(fs::OpenFile(&f, path, fs::OpenMode_Read))) {
|
||||
return;
|
||||
}
|
||||
mitm_list_size = static_cast<size_t>(fread(mitm_list, 1, sizeof(mitm_list), f));
|
||||
fclose(f);
|
||||
ON_SCOPE_EXIT { fs::CloseFile(f); };
|
||||
|
||||
R_ABORT_UNLESS(fs::ReadFile(&mitm_list_size, f, 0, mitm_list, sizeof(mitm_list)));
|
||||
}
|
||||
|
||||
/* Validate read size. */
|
||||
|
@ -313,7 +315,7 @@ namespace ams::boot2 {
|
|||
}
|
||||
|
||||
/* Launch Atmosphere boot2, using NcmStorageId_None to force SD card boot. */
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::ProgramId::Boot2, ncm::StorageId::None), 0);
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Boot2, ncm::StorageId::None), 0);
|
||||
}
|
||||
|
||||
void LaunchPostSdCardBootPrograms() {
|
||||
|
@ -326,7 +328,7 @@ namespace ams::boot2 {
|
|||
}
|
||||
|
||||
/* Launch Atmosphere dmnt, using NcmStorageId_None to force SD card boot. */
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::ProgramId::Dmnt, ncm::StorageId::None), 0);
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Dmnt, ncm::StorageId::None), 0);
|
||||
|
||||
/* Check for and forward declare non-atmosphere mitm modules. */
|
||||
DetectAndDeclareFutureMitms();
|
||||
|
@ -336,7 +338,7 @@ namespace ams::boot2 {
|
|||
LaunchList(AdditionalMaintenanceLaunchPrograms, NumAdditionalMaintenanceLaunchPrograms);
|
||||
/* Starting in 7.0.0, npns is launched during maintenance boot. */
|
||||
if (hos::GetVersion() >= hos::Version_700) {
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::ProgramId::Npns, ncm::StorageId::BuiltInSystem), 0);
|
||||
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Npns, ncm::StorageId::BuiltInSystem), 0);
|
||||
}
|
||||
} else {
|
||||
LaunchList(AdditionalLaunchPrograms, NumAdditionalLaunchPrograms);
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace ams::cfg {
|
|||
.key_combination = KEY_R,
|
||||
.override_by_default = true,
|
||||
},
|
||||
.program_id = ncm::ProgramId::AppletPhotoViewer,
|
||||
.program_id = ncm::SystemAppletId::PhotoViewer,
|
||||
};
|
||||
|
||||
constexpr size_t MaxProgramOverrideKeys = 8;
|
||||
|
@ -269,7 +269,7 @@ namespace ams::cfg {
|
|||
}
|
||||
|
||||
inline bool IsAnyApplicationHblProgramId(ncm::ProgramId program_id) {
|
||||
return g_hbl_override_config.override_any_app && ncm::IsApplicationProgramId(program_id) && !IsAnySpecificHblProgramId(program_id);
|
||||
return g_hbl_override_config.override_any_app && ncm::IsApplicationId(program_id) && !IsAnySpecificHblProgramId(program_id);
|
||||
}
|
||||
|
||||
void ParseIniFile(util::ini::Handler handler, const char *path, void *user_ctx) {
|
||||
|
@ -320,7 +320,7 @@ namespace ams::cfg {
|
|||
}
|
||||
|
||||
/* For system modules and anything launched before the home menu, always override. */
|
||||
if (program_id < ncm::ProgramId::AppletStart || !pm::info::HasLaunchedProgram(ncm::ProgramId::AppletQlaunch)) {
|
||||
if (program_id < ncm::SystemAppletId::Start || !pm::info::HasLaunchedProgram(ncm::SystemAppletId::Qlaunch)) {
|
||||
status.SetProgramSpecific();
|
||||
return status;
|
||||
}
|
||||
|
|
127
libraries/libstratosphere/source/fs/fs_bis.cpp
Normal file
127
libraries/libstratosphere/source/fs/fs_bis.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
class BisCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable {
|
||||
private:
|
||||
const BisPartitionId id;
|
||||
public:
|
||||
explicit BisCommonMountNameGenerator(BisPartitionId i) : id(i) { /* ... */ }
|
||||
|
||||
virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override {
|
||||
/* Determine how much space we need. */
|
||||
const char *bis_mount_name = GetBisMountName(this->id);
|
||||
const size_t needed_size = strnlen(bis_mount_name, MountNameLengthMax) + 2;
|
||||
AMS_ABORT_UNLESS(dst_size >= needed_size);
|
||||
|
||||
/* Generate the name. */
|
||||
auto size = std::snprintf(dst, dst_size, "%s:", bis_mount_name);
|
||||
AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
|
||||
Result MountBisImpl(const char *name, BisPartitionId id, const char *root_path) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountNameAllowingReserved(name));
|
||||
|
||||
/* Open the partition. This uses libnx bindings. */
|
||||
/* Note: Nintendo ignores the root_path here. */
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenBisFileSystem(std::addressof(fs), static_cast<::FsBisPartitionId>(id), ""));
|
||||
|
||||
/* Allocate a new mountname generator. */
|
||||
auto generator = std::make_unique<BisCommonMountNameGenerator>(id);
|
||||
R_UNLESS(generator != nullptr, fs::ResultAllocationFailureInBisA());
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInBisB());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa), std::move(generator));
|
||||
}
|
||||
|
||||
Result SetBisRootForHostImpl(BisPartitionId id, const char *root_path) {
|
||||
/* Ensure the path isn't too long. */
|
||||
size_t len = strnlen(root_path, fs::EntryNameLengthMax + 1);
|
||||
R_UNLESS(len <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||
|
||||
fssrv::sf::Path sf_path;
|
||||
if (len > 0) {
|
||||
const bool ending_sep = PathTool::IsSeparator(root_path[len - 1]);
|
||||
FspPathPrintf(std::addressof(sf_path), "%s%s", root_path, ending_sep ? "" : "/");
|
||||
} else {
|
||||
sf_path.str[0] = '\x00';
|
||||
}
|
||||
|
||||
/* TODO: Libnx binding for fsSetBisRootForHost */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const char *GetBisMountName(BisPartitionId id) {
|
||||
switch (id) {
|
||||
case BisPartitionId::CalibrationFile: return impl::BisCalibrationFilePartitionMountName;
|
||||
case BisPartitionId::SafeMode: return impl::BisSafeModePartitionMountName;
|
||||
case BisPartitionId::User: return impl::BisUserPartitionMountName;
|
||||
case BisPartitionId::System: return impl::BisSystemPartitionMountName;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
Result MountBis(BisPartitionId id, const char *root_path) {
|
||||
return impl::MountBisImpl(GetBisMountName(id), id, root_path);
|
||||
}
|
||||
|
||||
Result MountBis(const char *name, BisPartitionId id) {
|
||||
return impl::MountBisImpl(name, id, nullptr);
|
||||
}
|
||||
|
||||
void SetBisRootForHost(BisPartitionId id, const char *root_path) {
|
||||
R_ABORT_UNLESS(impl::SetBisRootForHostImpl(id, root_path));
|
||||
}
|
||||
|
||||
Result OpenBisPartition(std::unique_ptr<fs::IStorage> *out, BisPartitionId id) {
|
||||
/* Open the partition. This uses libnx bindings. */
|
||||
FsStorage s;
|
||||
R_TRY(fsOpenBisStorage(std::addressof(s), static_cast<::FsBisPartitionId>(id)));
|
||||
|
||||
/* Allocate a new storage wrapper. */
|
||||
auto storage = std::make_unique<RemoteStorage>(s);
|
||||
R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInBisC());
|
||||
|
||||
*out = std::move(storage);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result InvalidateBisCache() {
|
||||
/* TODO: Libnx binding for this command. */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
}
|
44
libraries/libstratosphere/source/fs/fs_code.cpp
Normal file
44
libraries/libstratosphere/source/fs/fs_code.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
Result MountCode(const char *name, const char *path, ncm::ProgramId program_id) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountName(name));
|
||||
|
||||
/* Validate the path isn't null. */
|
||||
R_UNLESS(path != nullptr, fs::ResultInvalidPath());
|
||||
|
||||
/* Print a path suitable for the remove service. */
|
||||
fssrv::sf::Path sf_path;
|
||||
R_TRY(FspPathPrintf(std::addressof(sf_path), "%s", path));
|
||||
|
||||
/* Open the filesystem using libnx bindings. */
|
||||
::FsFileSystem fs;
|
||||
R_TRY(fsldrOpenCodeFileSystem(program_id.value, sf_path.str, std::addressof(fs)));
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInCodeA());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa));
|
||||
}
|
||||
|
||||
}
|
74
libraries/libstratosphere/source/fs/fs_content.cpp
Normal file
74
libraries/libstratosphere/source/fs/fs_content.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
impl::FileSystemProxyType ConvertToFileSystemProxyType(ContentType content_type) {
|
||||
switch (content_type) {
|
||||
case ContentType_Control: return impl::FileSystemProxyType_Control;
|
||||
case ContentType_Manual: return impl::FileSystemProxyType_Manual;
|
||||
case ContentType_Meta: return impl::FileSystemProxyType_Meta;
|
||||
case ContentType_Data: return impl::FileSystemProxyType_Data;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
Result MountContentImpl(const char *name, const char *path, u64 id, impl::FileSystemProxyType type) {
|
||||
/* Open a filesystem using libnx bindings. */
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), id, static_cast<::FsFileSystemType>(type), path));
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInContentA());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa));
|
||||
}
|
||||
|
||||
Result MountContent(const char *name, const char *path, u64 id, ContentType content_type) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountNameAllowingReserved(name));
|
||||
|
||||
/* Validate the path. */
|
||||
R_UNLESS(path != nullptr, fs::ResultInvalidPath());
|
||||
|
||||
/* Mount the content. */
|
||||
return MountContentImpl(name, path, id, ConvertToFileSystemProxyType(content_type));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result MountContent(const char *name, const char *path, ContentType content_type) {
|
||||
/* This API only supports mounting Meta content. */
|
||||
AMS_ABORT_UNLESS(content_type == ContentType_Meta);
|
||||
|
||||
return MountContent(name, path, ncm::InvalidProgramId, content_type);
|
||||
}
|
||||
|
||||
Result MountContent(const char *name, const char *path, ncm::ProgramId id, ContentType content_type) {
|
||||
return MountContent(name, path, id.value, content_type);
|
||||
}
|
||||
|
||||
Result MountContent(const char *name, const char *path, ncm::DataId id, ContentType content_type) {
|
||||
return MountContent(name, path, id.value, content_type);
|
||||
}
|
||||
|
||||
}
|
93
libraries/libstratosphere/source/fs/fs_content_storage.cpp
Normal file
93
libraries/libstratosphere/source/fs/fs_content_storage.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
class ContentStorageCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable {
|
||||
private:
|
||||
const ContentStorageId id;
|
||||
public:
|
||||
explicit ContentStorageCommonMountNameGenerator(ContentStorageId i) : id(i) { /* ... */ }
|
||||
|
||||
virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override {
|
||||
/* Determine how much space we need. */
|
||||
const size_t needed_size = strnlen(GetContentStorageMountName(id), MountNameLengthMax) + 2;
|
||||
AMS_ABORT_UNLESS(dst_size >= needed_size);
|
||||
|
||||
/* Generate the name. */
|
||||
auto size = std::snprintf(dst, dst_size, "%s:", GetContentStorageMountName(id));
|
||||
AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const char *GetContentStorageMountName(ContentStorageId id) {
|
||||
switch (id) {
|
||||
case ContentStorageId::System: return impl::ContentStorageSystemMountName;
|
||||
case ContentStorageId::User: return impl::ContentStorageUserMountName;
|
||||
case ContentStorageId::SdCard: return impl::ContentStorageSdCardMountName;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
Result MountContentStorage(ContentStorageId id) {
|
||||
return MountContentStorage(GetContentStorageMountName(id), id);
|
||||
}
|
||||
|
||||
Result MountContentStorage(const char *name, ContentStorageId id) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountNameAllowingReserved(name));
|
||||
|
||||
/* It can take some time for the system partition to be ready (if it's on the SD card). */
|
||||
/* Thus, we will retry up to 10 times, waiting one second each time. */
|
||||
constexpr size_t MaxRetries = 10;
|
||||
constexpr u64 RetryInterval = 1'000'000'000ul;
|
||||
|
||||
/* Mount the content storage, use libnx bindings. */
|
||||
::FsFileSystem fs;
|
||||
for (size_t i = 0; i < MaxRetries; i++) {
|
||||
R_TRY_CATCH(fsOpenContentStorageFileSystem(std::addressof(fs), static_cast<::FsContentStorageId>(id))) {
|
||||
R_CATCH(fs::ResultSystemPartitionNotReady) {
|
||||
if (i < MaxRetries - 1) {
|
||||
/* TODO: os::SleepThread */
|
||||
svcSleepThread(RetryInterval);
|
||||
} else {
|
||||
return fs::ResultSystemPartitionNotReady();
|
||||
}
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInContentStorageA());
|
||||
|
||||
/* Allocate a new mountname generator. */
|
||||
auto generator = std::make_unique<ContentStorageCommonMountNameGenerator>(id);
|
||||
R_UNLESS(generator != nullptr, fs::ResultAllocationFailureInContentStorageB());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa), std::move(generator));
|
||||
}
|
||||
|
||||
}
|
89
libraries/libstratosphere/source/fs/fs_data.cpp
Normal file
89
libraries/libstratosphere/source/fs/fs_data.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
Result OpenDataStorageByDataId(std::unique_ptr<ams::fs::IStorage> *out, ncm::DataId data_id, ncm::StorageId storage_id) {
|
||||
/* Open storage using libnx bindings. */
|
||||
::FsStorage s;
|
||||
R_TRY_CATCH(fsOpenDataStorageByDataId(std::addressof(s), data_id.value, static_cast<::NcmStorageId>(storage_id))) {
|
||||
R_CONVERT(ncm::ResultContentMetaNotFound, fs::ResultTargetNotFound());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
auto storage = std::make_unique<RemoteStorage>(s);
|
||||
R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInDataA());
|
||||
|
||||
*out = std::move(storage);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result MountDataImpl(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size, bool use_cache, bool use_data_cache, bool use_path_cache) {
|
||||
std::unique_ptr<fs::IStorage> storage;
|
||||
R_TRY(OpenDataStorageByDataId(std::addressof(storage), data_id, storage_id));
|
||||
|
||||
auto fs = std::make_unique<RomFsFileSystem>();
|
||||
R_UNLESS(fs != nullptr, fs::ResultAllocationFailureInDataB());
|
||||
R_TRY(fs->Initialize(std::move(storage), cache_buffer, cache_size, use_cache));
|
||||
|
||||
return fsa::Register(name, std::move(fs), nullptr, use_data_cache, use_path_cache, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result QueryMountDataCacheSize(size_t *out, ncm::DataId data_id, ncm::StorageId storage_id) {
|
||||
R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
std::unique_ptr<fs::IStorage> storage;
|
||||
R_TRY(OpenDataStorageByDataId(std::addressof(storage), data_id, storage_id));
|
||||
|
||||
size_t size = 0;
|
||||
R_TRY(RomFsFileSystem::GetRequiredWorkingMemorySize(std::addressof(size), storage.get()));
|
||||
|
||||
constexpr size_t MinimumCacheSize = 32;
|
||||
*out = std::max(size, MinimumCacheSize);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountName(name));
|
||||
|
||||
return MountDataImpl(name, data_id, storage_id, nullptr, 0, false, false, false);
|
||||
}
|
||||
|
||||
Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountName(name));
|
||||
|
||||
R_UNLESS(cache_buffer != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
return MountDataImpl(name, data_id, storage_id, cache_buffer, cache_size, true, false, false);
|
||||
}
|
||||
|
||||
Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size, bool use_data_cache, bool use_path_cache) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountName(name));
|
||||
|
||||
R_UNLESS(cache_buffer != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
return MountDataImpl(name, data_id, storage_id, cache_buffer, cache_size, true, use_data_cache, use_path_cache);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
s64 HierarchicalRomFileTable::QueryDirectoryEntryStorageSize(u32 count) {
|
||||
const size_t real_count = count + ReservedDirectoryCount;
|
||||
return DirectoryEntryMapTable::QueryKeyValueStorageSize(real_count) + real_count * (RomPathTool::MaxPathLength + 1) * sizeof(RomPathChar);
|
||||
}
|
||||
|
||||
s64 HierarchicalRomFileTable::QueryDirectoryEntryBucketStorageSize(s64 count) {
|
||||
return DirectoryEntryMapTable::QueryBucketStorageSize(count);
|
||||
}
|
||||
|
||||
s64 HierarchicalRomFileTable::QueryFileEntryStorageSize(u32 count) {
|
||||
return FileEntryMapTable::QueryKeyValueStorageSize(count) + count * (RomPathTool::MaxPathLength + 1) * sizeof(RomPathChar);
|
||||
}
|
||||
|
||||
s64 HierarchicalRomFileTable::QueryFileEntryBucketStorageSize(s64 count) {
|
||||
return FileEntryMapTable::QueryBucketStorageSize(count);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::Format(SubStorage dir_bucket, SubStorage file_bucket) {
|
||||
s64 dir_bucket_size;
|
||||
R_TRY(dir_bucket.GetSize(std::addressof(dir_bucket_size)));
|
||||
R_TRY(DirectoryEntryMapTable::Format(dir_bucket, DirectoryEntryMapTable::QueryBucketCount(dir_bucket_size)));
|
||||
|
||||
s64 file_bucket_size;
|
||||
R_TRY(file_bucket.GetSize(std::addressof(file_bucket_size)));
|
||||
R_TRY(FileEntryMapTable::Format(file_bucket, FileEntryMapTable::QueryBucketCount(file_bucket_size)));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
HierarchicalRomFileTable::HierarchicalRomFileTable() { /* ... */ }
|
||||
|
||||
Result HierarchicalRomFileTable::Initialize(SubStorage dir_bucket, SubStorage dir_entry, SubStorage file_bucket, SubStorage file_entry) {
|
||||
s64 dir_bucket_size;
|
||||
R_TRY(dir_bucket.GetSize(std::addressof(dir_bucket_size)));
|
||||
R_TRY(this->dir_table.Initialize(dir_bucket, DirectoryEntryMapTable::QueryBucketCount(dir_bucket_size), dir_entry));
|
||||
|
||||
s64 file_bucket_size;
|
||||
R_TRY(file_bucket.GetSize(std::addressof(file_bucket_size)));
|
||||
R_TRY(this->file_table.Initialize(file_bucket, FileEntryMapTable::QueryBucketCount(file_bucket_size), file_entry));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void HierarchicalRomFileTable::Finalize() {
|
||||
this->dir_table.Finalize();
|
||||
this->file_table.Finalize();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::CreateRootDirectory() {
|
||||
Position root_pos = RootPosition;
|
||||
EntryKey root_key = {};
|
||||
root_key.key.parent = root_pos;
|
||||
RomPathTool::InitializeRomEntryName(std::addressof(root_key.name));
|
||||
RomDirectoryEntry root_entry = {
|
||||
.next = InvalidPosition,
|
||||
.dir = InvalidPosition,
|
||||
.file = InvalidPosition,
|
||||
};
|
||||
return this->dir_table.Add(std::addressof(root_pos), root_key, root_entry);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::CreateDirectory(RomDirectoryId *out, const RomPathChar *path, const DirectoryInfo &info) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey new_key = {};
|
||||
R_TRY(this->FindDirectoryRecursive(std::addressof(new_key), std::addressof(parent_entry), path));
|
||||
|
||||
R_TRY(this->CheckSameEntryExists(new_key, fs::ResultDbmAlreadyExists()));
|
||||
|
||||
RomDirectoryEntry new_entry = {
|
||||
.next = InvalidPosition,
|
||||
.dir = InvalidPosition,
|
||||
.file = InvalidPosition,
|
||||
};
|
||||
|
||||
Position new_pos = 0;
|
||||
R_TRY_CATCH(this->dir_table.Add(std::addressof(new_pos), new_key, new_entry)) {
|
||||
R_CONVERT(fs::ResultDbmKeyFull, fs::ResultDbmDirectoryEntryFull())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out = ConvertToDirectoryId(new_pos);
|
||||
|
||||
if (parent_entry.dir == InvalidPosition) {
|
||||
parent_entry.dir = new_pos;
|
||||
|
||||
R_TRY(this->dir_table.SetByPosition(new_key.key.parent, parent_entry));
|
||||
} else {
|
||||
Position cur_pos = parent_entry.dir;
|
||||
while (true) {
|
||||
RomEntryKey cur_key = {};
|
||||
RomDirectoryEntry cur_entry = {};
|
||||
R_TRY(this->dir_table.GetByPosition(std::addressof(cur_key), std::addressof(cur_entry), cur_pos));
|
||||
|
||||
if (cur_entry.next == InvalidPosition) {
|
||||
cur_entry.next = new_pos;
|
||||
|
||||
R_TRY(this->dir_table.SetByPosition(cur_pos, cur_entry));
|
||||
break;
|
||||
}
|
||||
|
||||
cur_pos = cur_entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::CreateFile(RomFileId *out, const RomPathChar *path, const FileInfo &info) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey new_key = {};
|
||||
R_TRY(this->FindFileRecursive(std::addressof(new_key), std::addressof(parent_entry), path));
|
||||
|
||||
R_TRY(this->CheckSameEntryExists(new_key, fs::ResultDbmAlreadyExists()));
|
||||
|
||||
RomFileEntry new_entry = {
|
||||
.next = InvalidPosition,
|
||||
.info = info,
|
||||
};
|
||||
|
||||
Position new_pos = 0;
|
||||
R_TRY_CATCH(this->file_table.Add(std::addressof(new_pos), new_key, new_entry)) {
|
||||
R_CONVERT(fs::ResultDbmKeyFull, fs::ResultDbmFileEntryFull())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out = ConvertToFileId(new_pos);
|
||||
|
||||
if (parent_entry.file == InvalidPosition) {
|
||||
parent_entry.file = new_pos;
|
||||
|
||||
R_TRY(this->dir_table.SetByPosition(new_key.key.parent, parent_entry));
|
||||
} else {
|
||||
Position cur_pos = parent_entry.file;
|
||||
while (true) {
|
||||
RomEntryKey cur_key = {};
|
||||
RomFileEntry cur_entry = {};
|
||||
R_TRY(this->file_table.GetByPosition(std::addressof(cur_key), std::addressof(cur_entry), cur_pos));
|
||||
|
||||
if (cur_entry.next == InvalidPosition) {
|
||||
cur_entry.next = new_pos;
|
||||
|
||||
R_TRY(this->file_table.SetByPosition(cur_pos, cur_entry));
|
||||
break;
|
||||
}
|
||||
|
||||
cur_pos = cur_entry.next;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::ConvertPathToDirectoryId(RomDirectoryId *out, const RomPathChar *path) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey key = {};
|
||||
R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path));
|
||||
|
||||
Position pos = 0;
|
||||
RomDirectoryEntry entry = {};
|
||||
R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key));
|
||||
|
||||
*out = ConvertToDirectoryId(pos);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::ConvertPathToFileId(RomFileId *out, const RomPathChar *path) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey key = {};
|
||||
R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path));
|
||||
|
||||
Position pos = 0;
|
||||
RomFileEntry entry = {};
|
||||
R_TRY(this->GetFileEntry(std::addressof(pos), std::addressof(entry), key));
|
||||
|
||||
*out = ConvertToFileId(pos);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetDirectoryInformation(DirectoryInfo *out, const RomPathChar *path) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey key = {};
|
||||
R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path));
|
||||
|
||||
return this->GetDirectoryInformation(out, key);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetDirectoryInformation(DirectoryInfo *out, RomDirectoryId id) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
RomDirectoryEntry entry = {};
|
||||
R_TRY(this->GetDirectoryEntry(std::addressof(entry), id));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::OpenFile(FileInfo *out, const RomPathChar *path) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey key = {};
|
||||
R_TRY(this->FindFileRecursive(std::addressof(key), std::addressof(parent_entry), path));
|
||||
|
||||
return this->OpenFile(out, key);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::OpenFile(FileInfo *out, RomFileId id) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
RomFileEntry entry = {};
|
||||
R_TRY(this->GetFileEntry(std::addressof(entry), id));
|
||||
|
||||
*out = entry.info;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindOpen(FindPosition *out, const RomPathChar *path) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomDirectoryEntry parent_entry = {};
|
||||
EntryKey key = {};
|
||||
R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path));
|
||||
|
||||
return this->FindOpen(out, key);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindOpen(FindPosition *out, RomDirectoryId id) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
out->next_dir = InvalidPosition;
|
||||
out->next_file = InvalidPosition;
|
||||
|
||||
RomDirectoryEntry entry = {};
|
||||
R_TRY(this->GetDirectoryEntry(std::addressof(entry), id));
|
||||
|
||||
out->next_dir = entry.dir;
|
||||
out->next_file = entry.file;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindNextDirectory(RomPathChar *out, FindPosition *find, size_t length) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(find != nullptr);
|
||||
AMS_ASSERT(length > RomPathTool::MaxPathLength);
|
||||
|
||||
R_UNLESS(find->next_dir != InvalidPosition, fs::ResultDbmFindFinished());
|
||||
|
||||
RomEntryKey key = {};
|
||||
RomDirectoryEntry entry = {};
|
||||
size_t aux_size = 0;
|
||||
R_TRY(this->dir_table.GetByPosition(std::addressof(key), std::addressof(entry), out, std::addressof(aux_size), find->next_dir));
|
||||
AMS_ASSERT(aux_size / sizeof(RomPathChar) <= RomPathTool::MaxPathLength);
|
||||
|
||||
out[aux_size / sizeof(RomPathChar)] = RomStringTraits::NullTerminator;
|
||||
|
||||
find->next_dir = entry.next;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindNextFile(RomPathChar *out, FindPosition *find, size_t length) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(find != nullptr);
|
||||
AMS_ASSERT(length > RomPathTool::MaxPathLength);
|
||||
|
||||
R_UNLESS(find->next_file != InvalidPosition, fs::ResultDbmFindFinished());
|
||||
|
||||
RomEntryKey key = {};
|
||||
RomFileEntry entry = {};
|
||||
size_t aux_size = 0;
|
||||
R_TRY(this->file_table.GetByPosition(std::addressof(key), std::addressof(entry), out, std::addressof(aux_size), find->next_file));
|
||||
AMS_ASSERT(aux_size / sizeof(RomPathChar) <= RomPathTool::MaxPathLength);
|
||||
|
||||
out[aux_size / sizeof(RomPathChar)] = RomStringTraits::NullTerminator;
|
||||
|
||||
find->next_file = entry.next;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::QueryRomFileSystemSize(s64 *out_dir_entry_size, s64 *out_file_entry_size) {
|
||||
AMS_ASSERT(out_dir_entry_size != nullptr);
|
||||
AMS_ASSERT(out_file_entry_size != nullptr);
|
||||
|
||||
*out_dir_entry_size = this->dir_table.GetTotalEntrySize();
|
||||
*out_file_entry_size = this->file_table.GetTotalEntrySize();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetGrandParent(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, Position pos, RomPathTool::RomEntryName name, const RomPathChar *path) {
|
||||
AMS_ASSERT(out_pos != nullptr);
|
||||
AMS_ASSERT(out_dir_key != nullptr);
|
||||
AMS_ASSERT(out_dir_entry != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomEntryKey gp_key = {};
|
||||
RomDirectoryEntry gp_entry = {};
|
||||
R_TRY(this->dir_table.GetByPosition(std::addressof(gp_key), std::addressof(gp_entry), pos));
|
||||
out_dir_key->key.parent = gp_key.parent;
|
||||
|
||||
R_TRY(RomPathTool::GetParentDirectoryName(std::addressof(out_dir_key->name), name, path));
|
||||
|
||||
R_TRY(this->GetDirectoryEntry(out_pos, out_dir_entry, *out_dir_key));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindParentDirectoryRecursive(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, RomPathTool::PathParser *parser, const RomPathChar *path) {
|
||||
AMS_ASSERT(out_pos != nullptr);
|
||||
AMS_ASSERT(out_dir_key != nullptr);
|
||||
AMS_ASSERT(out_dir_entry != nullptr);
|
||||
AMS_ASSERT(parser != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
Position dir_pos = RootPosition;
|
||||
EntryKey dir_key = {};
|
||||
RomDirectoryEntry dir_entry = {};
|
||||
dir_key.key.parent = RootPosition;
|
||||
|
||||
R_TRY(parser->GetNextDirectoryName(std::addressof(dir_key.name)));
|
||||
R_TRY(this->GetDirectoryEntry(std::addressof(dir_pos), std::addressof(dir_entry), dir_key));
|
||||
|
||||
Position parent_pos = dir_pos;
|
||||
while (!parser->IsFinished()) {
|
||||
EntryKey old_key = dir_key;
|
||||
|
||||
R_TRY(parser->GetNextDirectoryName(std::addressof(dir_key.name)));
|
||||
|
||||
if (RomPathTool::IsCurrentDirectory(dir_key.name)) {
|
||||
dir_key = old_key;
|
||||
continue;
|
||||
} else if (RomPathTool::IsParentDirectory(dir_key.name)) {
|
||||
R_UNLESS(parent_pos != RootPosition, fs::ResultDirectoryUnobtainable());
|
||||
|
||||
R_TRY(this->GetGrandParent(std::addressof(parent_pos), std::addressof(dir_key), std::addressof(dir_entry), dir_key.key.parent, dir_key.name, path));
|
||||
} else {
|
||||
dir_key.key.parent = parent_pos;
|
||||
R_TRY_CATCH(this->GetDirectoryEntry(std::addressof(dir_pos), std::addressof(dir_entry), dir_key)) {
|
||||
R_CONVERT(fs::ResultDbmInvalidOperation, fs::ResultDbmNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
parent_pos = dir_pos;
|
||||
}
|
||||
}
|
||||
|
||||
*out_pos = parent_pos;
|
||||
*out_dir_key = dir_key;
|
||||
*out_dir_entry = dir_entry;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindPathRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, bool is_dir, const RomPathChar *path) {
|
||||
AMS_ASSERT(out_key != nullptr);
|
||||
AMS_ASSERT(out_dir_entry != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomPathTool::PathParser parser;
|
||||
R_TRY(parser.Initialize(path));
|
||||
|
||||
EntryKey parent_key = {};
|
||||
Position parent_pos = 0;
|
||||
R_TRY(this->FindParentDirectoryRecursive(std::addressof(parent_pos), std::addressof(parent_key), out_dir_entry, std::addressof(parser), path));
|
||||
|
||||
if (is_dir) {
|
||||
RomPathTool::RomEntryName name = {};
|
||||
R_TRY(parser.GetAsDirectoryName(std::addressof(name)));
|
||||
|
||||
if (RomPathTool::IsCurrentDirectory(name)) {
|
||||
*out_key = parent_key;
|
||||
if (out_key->key.parent != RootPosition) {
|
||||
Position pos = 0;
|
||||
R_TRY(this->GetGrandParent(std::addressof(pos), std::addressof(parent_key), out_dir_entry, out_key->key.parent, out_key->name, path));
|
||||
}
|
||||
} else if (RomPathTool::IsParentDirectory(name)) {
|
||||
R_UNLESS(parent_pos != RootPosition, fs::ResultDirectoryUnobtainable());
|
||||
|
||||
Position pos = 0;
|
||||
RomDirectoryEntry cur_entry = {};
|
||||
R_TRY(this->GetGrandParent(std::addressof(pos), out_key, std::addressof(cur_entry), parent_key.key.parent, parent_key.name, path));
|
||||
|
||||
if (out_key->key.parent != RootPosition) {
|
||||
R_TRY(this->GetGrandParent(std::addressof(pos), std::addressof(parent_key), out_dir_entry, out_key->key.parent, out_key->name, path));
|
||||
}
|
||||
} else {
|
||||
out_key->name = name;
|
||||
out_key->key.parent = (out_key->name.length > 0) ? parent_pos : RootPosition;
|
||||
}
|
||||
} else {
|
||||
{
|
||||
RomPathTool::RomEntryName name = {};
|
||||
R_TRY(parser.GetAsDirectoryName(std::addressof(name)));
|
||||
R_UNLESS(!RomPathTool::IsParentDirectory(name) || parent_pos != RootPosition, fs::ResultDirectoryUnobtainable());
|
||||
}
|
||||
|
||||
R_UNLESS(!parser.IsDirectoryPath(), fs::ResultDbmInvalidOperation());
|
||||
|
||||
out_key->key.parent = parent_pos;
|
||||
R_TRY(parser.GetAsFileName(std::addressof(out_key->name)));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindDirectoryRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path) {
|
||||
AMS_ASSERT(out_key != nullptr);
|
||||
AMS_ASSERT(out_dir_entry != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
return this->FindPathRecursive(out_key, out_dir_entry, true, path);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindFileRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path) {
|
||||
AMS_ASSERT(out_key != nullptr);
|
||||
AMS_ASSERT(out_dir_entry != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
return this->FindPathRecursive(out_key, out_dir_entry, false, path);
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::CheckSameEntryExists(const EntryKey &key, Result if_exists) {
|
||||
/* Check dir */
|
||||
{
|
||||
Position pos = InvalidPosition;
|
||||
RomDirectoryEntry entry = {};
|
||||
const Result get_res = this->dir_table.Get(std::addressof(pos), std::addressof(entry), key);
|
||||
if (!fs::ResultDbmKeyNotFound::Includes(get_res)) {
|
||||
R_TRY(get_res);
|
||||
return if_exists;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check file */
|
||||
{
|
||||
Position pos = InvalidPosition;
|
||||
RomFileEntry entry = {};
|
||||
const Result get_res = this->file_table.Get(std::addressof(pos), std::addressof(entry), key);
|
||||
if (!fs::ResultDbmKeyNotFound::Includes(get_res)) {
|
||||
R_TRY(get_res);
|
||||
return if_exists;
|
||||
}
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetDirectoryEntry(Position *out_pos, RomDirectoryEntry *out_entry, const EntryKey &key) {
|
||||
AMS_ASSERT(out_pos != nullptr);
|
||||
AMS_ASSERT(out_entry != nullptr);
|
||||
|
||||
const Result dir_res = this->dir_table.Get(out_pos, out_entry, key);
|
||||
R_UNLESS(R_FAILED(dir_res), dir_res);
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), dir_res);
|
||||
|
||||
Position pos = 0;
|
||||
RomFileEntry entry = {};
|
||||
const Result file_res = this->file_table.Get(std::addressof(pos), std::addressof(entry), key);
|
||||
R_UNLESS(R_FAILED(file_res), fs::ResultDbmInvalidOperation());
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), fs::ResultDbmDirectoryNotFound());
|
||||
return file_res;
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetDirectoryEntry(RomDirectoryEntry *out_entry, RomDirectoryId id) {
|
||||
AMS_ASSERT(out_entry != nullptr);
|
||||
Position pos = ConvertToPosition(id);
|
||||
|
||||
RomEntryKey key = {};
|
||||
const Result dir_res = this->dir_table.GetByPosition(std::addressof(key), out_entry, pos);
|
||||
R_UNLESS(R_FAILED(dir_res), dir_res);
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), dir_res);
|
||||
|
||||
RomFileEntry entry = {};
|
||||
const Result file_res = this->file_table.GetByPosition(std::addressof(key), std::addressof(entry), pos);
|
||||
R_UNLESS(R_FAILED(file_res), fs::ResultDbmInvalidOperation());
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), fs::ResultDbmDirectoryNotFound());
|
||||
return file_res;
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetFileEntry(Position *out_pos, RomFileEntry *out_entry, const EntryKey &key) {
|
||||
AMS_ASSERT(out_pos != nullptr);
|
||||
AMS_ASSERT(out_entry != nullptr);
|
||||
|
||||
const Result file_res = this->file_table.Get(out_pos, out_entry, key);
|
||||
R_UNLESS(R_FAILED(file_res), file_res);
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), file_res);
|
||||
|
||||
Position pos = 0;
|
||||
RomDirectoryEntry entry = {};
|
||||
const Result dir_res = this->dir_table.Get(std::addressof(pos), std::addressof(entry), key);
|
||||
R_UNLESS(R_FAILED(dir_res), fs::ResultDbmInvalidOperation());
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), fs::ResultDbmFileNotFound());
|
||||
return dir_res;
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetFileEntry(RomFileEntry *out_entry, RomFileId id) {
|
||||
AMS_ASSERT(out_entry != nullptr);
|
||||
Position pos = ConvertToPosition(id);
|
||||
|
||||
RomEntryKey key = {};
|
||||
const Result file_res = this->file_table.GetByPosition(std::addressof(key), out_entry, pos);
|
||||
R_UNLESS(R_FAILED(file_res), file_res);
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), file_res);
|
||||
|
||||
RomDirectoryEntry entry = {};
|
||||
const Result dir_res = this->dir_table.GetByPosition(std::addressof(key), std::addressof(entry), pos);
|
||||
R_UNLESS(R_FAILED(dir_res), fs::ResultDbmInvalidOperation());
|
||||
R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), fs::ResultDbmFileNotFound());
|
||||
return dir_res;
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::GetDirectoryInformation(DirectoryInfo *out, const EntryKey &key) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
Position pos = 0;
|
||||
RomDirectoryEntry entry = {};
|
||||
R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::OpenFile(FileInfo *out, const EntryKey &key) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
Position pos = 0;
|
||||
RomFileEntry entry = {};
|
||||
R_TRY(this->GetFileEntry(std::addressof(pos), std::addressof(entry), key));
|
||||
|
||||
*out = entry.info;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HierarchicalRomFileTable::FindOpen(FindPosition *out, const EntryKey &key) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
out->next_dir = InvalidPosition;
|
||||
out->next_file = InvalidPosition;
|
||||
|
||||
Position pos = 0;
|
||||
RomDirectoryEntry entry = {};
|
||||
R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key));
|
||||
|
||||
out->next_dir = entry.dir;
|
||||
out->next_file = entry.file;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
195
libraries/libstratosphere/source/fs/fs_dbm_rom_path_tool.cpp
Normal file
195
libraries/libstratosphere/source/fs/fs_dbm_rom_path_tool.cpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs::RomPathTool {
|
||||
|
||||
Result PathParser::Initialize(const RomPathChar *path) {
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
/* Require paths start with a separator, and skip repeated separators. */
|
||||
R_UNLESS(IsSeparator(path[0]), fs::ResultDbmInvalidPathFormat());
|
||||
while (IsSeparator(path[1])) {
|
||||
path++;
|
||||
}
|
||||
|
||||
this->prev_path_start = path;
|
||||
this->prev_path_end = path;
|
||||
this->next_path = path + 1;
|
||||
while (IsSeparator(this->next_path[0])) {
|
||||
this->next_path++;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void PathParser::Finalize() {
|
||||
this->prev_path_start = nullptr;
|
||||
this->prev_path_end = nullptr;
|
||||
this->next_path = nullptr;
|
||||
this->finished = false;
|
||||
}
|
||||
|
||||
bool PathParser::IsFinished() const {
|
||||
return this->finished;
|
||||
}
|
||||
|
||||
bool PathParser::IsDirectoryPath() const {
|
||||
AMS_ASSERT(this->next_path != nullptr);
|
||||
if (IsNullTerminator(this->next_path[0]) && IsSeparator(this->next_path[-1])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsCurrentDirectory(this->next_path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsParentDirectory(this->next_path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Result PathParser::GetAsDirectoryName(RomEntryName *out) const {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(this->prev_path_start != nullptr);
|
||||
AMS_ASSERT(this->prev_path_end != nullptr);
|
||||
AMS_ASSERT(this->next_path != nullptr);
|
||||
|
||||
const size_t len = this->prev_path_end - this->prev_path_start;
|
||||
R_UNLESS(len <= MaxPathLength, fs::ResultDbmDirectoryNameTooLong());
|
||||
|
||||
out->length = len;
|
||||
out->path = this->prev_path_start;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PathParser::GetAsFileName(RomEntryName *out) const {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(this->prev_path_start != nullptr);
|
||||
AMS_ASSERT(this->prev_path_end != nullptr);
|
||||
AMS_ASSERT(this->next_path != nullptr);
|
||||
|
||||
const size_t len = this->prev_path_end - this->prev_path_start;
|
||||
R_UNLESS(len <= MaxPathLength, fs::ResultDbmFileNameTooLong());
|
||||
|
||||
out->length = len;
|
||||
out->path = this->prev_path_start;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PathParser::GetNextDirectoryName(RomEntryName *out) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(this->prev_path_start != nullptr);
|
||||
AMS_ASSERT(this->prev_path_end != nullptr);
|
||||
AMS_ASSERT(this->next_path != nullptr);
|
||||
|
||||
/* Set the current path to output. */
|
||||
out->length = this->prev_path_end - this->prev_path_start;
|
||||
out->path = this->prev_path_start;
|
||||
|
||||
/* Parse the next path. */
|
||||
this->prev_path_start = this->next_path;
|
||||
const RomPathChar *cur = this->next_path;
|
||||
for (size_t name_len = 0; true; name_len++) {
|
||||
if (IsSeparator(cur[name_len])) {
|
||||
R_UNLESS(name_len < MaxPathLength, fs::ResultDbmDirectoryNameTooLong());
|
||||
|
||||
this->prev_path_end = cur + name_len;
|
||||
this->next_path = this->prev_path_end + 1;
|
||||
|
||||
while (IsSeparator(this->next_path[0])) {
|
||||
this->next_path++;
|
||||
}
|
||||
if (IsNullTerminator(this->next_path[0])) {
|
||||
this->finished = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsNullTerminator(cur[name_len])) {
|
||||
this->finished = true;
|
||||
this->prev_path_end = this->next_path = cur + name_len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
AMS_ASSERT(p != nullptr);
|
||||
|
||||
const RomPathChar *start = cur.path;
|
||||
const RomPathChar *end = cur.path + cur.length - 1;
|
||||
|
||||
s32 depth = 1;
|
||||
if (IsParentDirectory(cur)) {
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (cur.path > p) {
|
||||
size_t len = 0;
|
||||
const RomPathChar *head = cur.path - 1;
|
||||
while (head >= p) {
|
||||
if (IsSeparator(*head)) {
|
||||
if (IsCurrentDirectory(head + 1, len)) {
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (IsParentDirectory(head + 1, len)) {
|
||||
depth += 2;
|
||||
}
|
||||
|
||||
if (depth == 0) {
|
||||
start = head + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
while (IsSeparator(*head)) {
|
||||
head--;
|
||||
}
|
||||
|
||||
end = head;
|
||||
len = 0;
|
||||
depth--;
|
||||
}
|
||||
|
||||
len++;
|
||||
head--;
|
||||
}
|
||||
|
||||
R_UNLESS(depth == 0, fs::ResultDirectoryUnobtainable());
|
||||
|
||||
if (head == p) {
|
||||
start = p + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (end <= p) {
|
||||
out->path = p;
|
||||
out->length = 0;
|
||||
} else {
|
||||
out->path = start;
|
||||
out->length = end - start + 1;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
36
libraries/libstratosphere/source/fs/fs_file_path_hash.hpp
Normal file
36
libraries/libstratosphere/source/fs/fs_file_path_hash.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
constexpr inline size_t FilePathHashSize = 4;
|
||||
|
||||
struct FilePathHash : public Newable {
|
||||
u8 data[FilePathHashSize];
|
||||
};
|
||||
static_assert(std::is_pod<FilePathHash>::value);
|
||||
|
||||
inline bool operator==(const FilePathHash &lhs, const FilePathHash &rhs) {
|
||||
return std::memcmp(lhs.data, rhs.data, FilePathHashSize) == 0;
|
||||
}
|
||||
|
||||
inline bool operator!=(const FilePathHash &lhs, const FilePathHash &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,13 +18,13 @@
|
|||
namespace ams::fs {
|
||||
|
||||
Result FileStorage::UpdateSize() {
|
||||
R_UNLESS(this->size == InvalidSize, ResultSuccess());
|
||||
R_SUCCEED_IF(this->size != InvalidSize);
|
||||
return this->base_file->GetSize(&this->size);
|
||||
}
|
||||
|
||||
Result FileStorage::Read(s64 offset, void *buffer, size_t size) {
|
||||
/* Immediately succeed if there's nothing to read. */
|
||||
R_UNLESS(size > 0, ResultSuccess());
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
/* Validate buffer. */
|
||||
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
|
||||
|
@ -41,7 +41,7 @@ namespace ams::fs {
|
|||
|
||||
Result FileStorage::Write(s64 offset, const void *buffer, size_t size) {
|
||||
/* Immediately succeed if there's nothing to write. */
|
||||
R_UNLESS(size > 0, ResultSuccess());
|
||||
R_SUCCEED_IF(size == 0);
|
||||
|
||||
/* Validate buffer. */
|
||||
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
|
||||
|
@ -86,7 +86,7 @@ namespace ams::fs {
|
|||
R_UNLESS(IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange());
|
||||
return this->base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size);
|
||||
default:
|
||||
return fs::ResultUnsupportedOperation();
|
||||
return fs::ResultUnsupportedOperationInFileStorageA();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
87
libraries/libstratosphere/source/fs/fs_game_card.cpp
Normal file
87
libraries/libstratosphere/source/fs/fs_game_card.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
const char *GetGameCardMountNameSuffix(GameCardPartition which) {
|
||||
switch (which) {
|
||||
case GameCardPartition::Update: return impl::GameCardFileSystemMountNameUpdateSuffix;
|
||||
case GameCardPartition::Normal: return impl::GameCardFileSystemMountNameNormalSuffix;
|
||||
case GameCardPartition::Secure: return impl::GameCardFileSystemMountNameSecureSuffix;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
class GameCardCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable {
|
||||
private:
|
||||
const GameCardHandle handle;
|
||||
const GameCardPartition partition;
|
||||
public:
|
||||
explicit GameCardCommonMountNameGenerator(GameCardHandle h, GameCardPartition p) : handle(h), partition(p) { /* ... */ }
|
||||
|
||||
virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override {
|
||||
/* Determine how much space we need. */
|
||||
const size_t needed_size = strnlen(impl::GameCardFileSystemMountName, MountNameLengthMax) + strnlen(GetGameCardMountNameSuffix(this->partition), MountNameLengthMax) + sizeof(GameCardHandle) * 2 + 2;
|
||||
AMS_ABORT_UNLESS(dst_size >= needed_size);
|
||||
|
||||
/* Generate the name. */
|
||||
auto size = std::snprintf(dst, dst_size, "%s%s%08x:", impl::GameCardFileSystemMountName, GetGameCardMountNameSuffix(this->partition), this->handle);
|
||||
AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Result GetGameCardHandle(GameCardHandle *out) {
|
||||
/* TODO: fs::DeviceOperator */
|
||||
/* Open a DeviceOperator. */
|
||||
::FsDeviceOperator d;
|
||||
R_TRY(fsOpenDeviceOperator(std::addressof(d)));
|
||||
ON_SCOPE_EXIT { fsDeviceOperatorClose(std::addressof(d)); };
|
||||
|
||||
/* Get the handle. */
|
||||
static_assert(sizeof(GameCardHandle) == sizeof(::FsGameCardHandle));
|
||||
return fsDeviceOperatorGetGameCardHandle(std::addressof(d), reinterpret_cast<::FsGameCardHandle *>(out));
|
||||
}
|
||||
|
||||
Result MountGameCardPartition(const char *name, GameCardHandle handle, GameCardPartition partition) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountNameAllowingReserved(name));
|
||||
|
||||
/* Open gamecard filesystem. This uses libnx bindings. */
|
||||
::FsFileSystem fs;
|
||||
const ::FsGameCardHandle _hnd = {handle};
|
||||
R_TRY(fsOpenGameCardFileSystem(std::addressof(fs), std::addressof(_hnd), static_cast<::FsGameCardPartition>(partition)));
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInGameCardC());
|
||||
|
||||
/* Allocate a new mountname generator. */
|
||||
auto generator = std::make_unique<GameCardCommonMountNameGenerator>(handle, partition);
|
||||
R_UNLESS(generator != nullptr, fs::ResultAllocationFailureInGameCardD());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa), std::move(generator));
|
||||
}
|
||||
|
||||
}
|
83
libraries/libstratosphere/source/fs/fs_memory_management.cpp
Normal file
83
libraries/libstratosphere/source/fs/fs_memory_management.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
bool g_used_default_allocator;
|
||||
|
||||
void *DefaultAllocate(size_t size) {
|
||||
g_used_default_allocator = true;
|
||||
return std::malloc(size);
|
||||
}
|
||||
|
||||
void DefaultDeallocate(void *ptr, size_t size) {
|
||||
std::free(ptr);
|
||||
}
|
||||
|
||||
os::Mutex g_lock;
|
||||
AllocateFunction g_allocate_func = DefaultAllocate;
|
||||
DeallocateFunction g_deallocate_func = DefaultDeallocate;
|
||||
|
||||
constexpr size_t RequiredAlignment = alignof(u64);
|
||||
|
||||
Result SetAllocatorImpl(AllocateFunction allocator, DeallocateFunction deallocator) {
|
||||
/* Ensure SetAllocator is used correctly. */
|
||||
R_UNLESS(g_allocate_func == DefaultAllocate, fs::ResultAllocatorAlreadyRegistered());
|
||||
R_UNLESS(g_deallocate_func == DefaultDeallocate, fs::ResultAllocatorAlreadyRegistered());
|
||||
R_UNLESS(allocator != nullptr, fs::ResultNullptrArgument());
|
||||
R_UNLESS(deallocator != nullptr, fs::ResultNullptrArgument());
|
||||
R_UNLESS(!g_used_default_allocator, fs::ResultDefaultAllocatorUsed());
|
||||
|
||||
/* Set allocators. */
|
||||
g_allocate_func = allocator;
|
||||
g_deallocate_func = deallocator;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SetAllocator(AllocateFunction allocator, DeallocateFunction deallocator) {
|
||||
R_ABORT_UNLESS(SetAllocatorImpl(allocator, deallocator));
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
|
||||
void *Allocate(size_t size) {
|
||||
void *ptr;
|
||||
{
|
||||
std::scoped_lock lk(g_lock);
|
||||
ptr = g_allocate_func(size);
|
||||
if (!util::IsAligned(reinterpret_cast<uintptr_t>(ptr), RequiredAlignment)) {
|
||||
R_ABORT_UNLESS(fs::ResultAllocatorAlignmentViolation());
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Deallocate(void *ptr, size_t size) {
|
||||
if (ptr == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lk(g_lock);
|
||||
g_deallocate_func(ptr, size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace ams::fs {
|
|||
const char c = *(cur++);
|
||||
|
||||
/* If terminated, we're done. */
|
||||
R_UNLESS(c != StringTraits::NullTerminator, ResultSuccess());
|
||||
R_SUCCEED_IF(PathTool::IsNullTerminator(c));
|
||||
|
||||
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
|
||||
/* We should do this. */
|
||||
|
|
31
libraries/libstratosphere/source/fs/fs_rights_id.cpp
Normal file
31
libraries/libstratosphere/source/fs/fs_rights_id.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/fs/fs_rights_id.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
Result GetRightsId(RightsId *out, const char *path) {
|
||||
static_assert(sizeof(RightsId) == sizeof(::FsRightsId));
|
||||
return fsGetRightsIdByPath(path, reinterpret_cast<::FsRightsId *>(out));
|
||||
}
|
||||
|
||||
Result GetRightsId(RightsId *out, u8 *out_key_generation, const char *path) {
|
||||
static_assert(sizeof(RightsId) == sizeof(::FsRightsId));
|
||||
return fsGetRightsIdAndKeyGenerationByPath(path, out_key_generation, reinterpret_cast<::FsRightsId *>(out));
|
||||
}
|
||||
|
||||
}
|
536
libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp
Normal file
536
libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
Result ConvertNcaCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultNcaCorrupted::Includes(res));
|
||||
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultInvalidNcaFileSystemType, fs::ResultInvalidRomNcaFileSystemType())
|
||||
R_CONVERT(fs::ResultInvalidAcidFileSize, fs::ResultInvalidRomAcidFileSize())
|
||||
R_CONVERT(fs::ResultInvalidAcidSize, fs::ResultInvalidRomAcidSize())
|
||||
R_CONVERT(fs::ResultInvalidAcid, fs::ResultInvalidRomAcid())
|
||||
R_CONVERT(fs::ResultAcidVerificationFailed, fs::ResultRomAcidVerificationFailed())
|
||||
R_CONVERT(fs::ResultInvalidNcaSignature, fs::ResultInvalidRomNcaSignature())
|
||||
R_CONVERT(fs::ResultNcaHeaderSignature1VerificationFailed, fs::ResultRomNcaHeaderSignature1VerificationFailed())
|
||||
R_CONVERT(fs::ResultNcaHeaderSignature2VerificationFailed, fs::ResultRomNcaHeaderSignature2VerificationFailed())
|
||||
R_CONVERT(fs::ResultNcaFsHeaderHashVerificationFailed, fs::ResultRomNcaFsHeaderHashVerificationFailed())
|
||||
R_CONVERT(fs::ResultInvalidNcaKeyIndex, fs::ResultInvalidRomNcaKeyIndex())
|
||||
R_CONVERT(fs::ResultInvalidNcaFsHeaderHashType, fs::ResultInvalidRomNcaFsHeaderHashType())
|
||||
R_CONVERT(fs::ResultInvalidNcaFsHeaderEncryptionType, fs::ResultInvalidRomNcaFsHeaderEncryptionType())
|
||||
R_CONVERT(fs::ResultInvalidHierarchicalSha256BlockSize, fs::ResultInvalidRomHierarchicalSha256BlockSize())
|
||||
R_CONVERT(fs::ResultInvalidHierarchicalSha256LayerCount, fs::ResultInvalidRomHierarchicalSha256LayerCount())
|
||||
R_CONVERT(fs::ResultHierarchicalSha256BaseStorageTooLarge, fs::ResultRomHierarchicalSha256BaseStorageTooLarge())
|
||||
R_CONVERT(fs::ResultHierarchicalSha256HashVerificationFailed, fs::ResultRomHierarchicalSha256HashVerificationFailed())
|
||||
R_CATCH_ALL() { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
AMS_ASSERT(false);
|
||||
return fs::ResultNcaCorrupted();
|
||||
}
|
||||
|
||||
Result ConvertIntegrityVerificationStorageCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultIntegrityVerificationStorageCorrupted::Includes(res));
|
||||
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultIncorrectIntegrityVerificationMagic, fs::ResultIncorrectRomIntegrityVerificationMagic())
|
||||
R_CONVERT(fs::ResultInvalidZeroHash, fs::ResultInvalidRomZeroHash())
|
||||
R_CONVERT(fs::ResultNonRealDataVerificationFailed, fs::ResultRomNonRealDataVerificationFailed())
|
||||
R_CONVERT(fs::ResultInvalidHierarchicalIntegrityVerificationLayerCount, fs::ResultInvalidRomHierarchicalIntegrityVerificationLayerCount())
|
||||
R_CONVERT(fs::ResultClearedRealDataVerificationFailed, fs::ResultClearedRomRealDataVerificationFailed())
|
||||
R_CONVERT(fs::ResultUnclearedRealDataVerificationFailed, fs::ResultUnclearedRomRealDataVerificationFailed())
|
||||
R_CATCH_ALL() { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
AMS_ASSERT(false);
|
||||
return fs::ResultIntegrityVerificationStorageCorrupted();
|
||||
}
|
||||
|
||||
Result ConvertBuiltInStorageCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultBuiltInStorageCorrupted::Includes(res));
|
||||
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultGptHeaderVerificationFailed, fs::ResultRomGptHeaderVerificationFailed())
|
||||
R_CATCH_ALL() { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
AMS_ASSERT(false);
|
||||
return fs::ResultBuiltInStorageCorrupted();
|
||||
}
|
||||
|
||||
Result ConvertPartitionFileSystemCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultPartitionFileSystemCorrupted::Includes(res));
|
||||
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultInvalidSha256PartitionHashTarget, fs::ResultInvalidRomSha256PartitionHashTarget())
|
||||
R_CONVERT(fs::ResultSha256PartitionHashVerificationFailed, fs::ResultRomSha256PartitionHashVerificationFailed())
|
||||
R_CONVERT(fs::ResultPartitionSignatureVerificationFailed, fs::ResultRomPartitionSignatureVerificationFailed())
|
||||
R_CONVERT(fs::ResultSha256PartitionSignatureVerificationFailed, fs::ResultRomSha256PartitionSignatureVerificationFailed())
|
||||
R_CONVERT(fs::ResultInvalidPartitionEntryOffset, fs::ResultInvalidRomPartitionEntryOffset())
|
||||
R_CONVERT(fs::ResultInvalidSha256PartitionMetaDataSize, fs::ResultInvalidRomSha256PartitionMetaDataSize())
|
||||
R_CATCH_ALL() { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
AMS_ASSERT(false);
|
||||
return fs::ResultPartitionFileSystemCorrupted();
|
||||
}
|
||||
|
||||
Result ConvertFatFileSystemCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultFatFileSystemCorrupted::Includes(res));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result ConvertHostFileSystemCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultHostFileSystemCorrupted::Includes(res));
|
||||
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultHostEntryCorrupted, fs::ResultRomHostEntryCorrupted())
|
||||
R_CONVERT(fs::ResultHostFileDataCorrupted, fs::ResultRomHostFileDataCorrupted())
|
||||
R_CONVERT(fs::ResultHostFileCorrupted, fs::ResultRomHostFileCorrupted())
|
||||
R_CONVERT(fs::ResultInvalidHostHandle, fs::ResultInvalidRomHostHandle())
|
||||
R_CATCH_ALL() { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
AMS_ASSERT(false);
|
||||
return fs::ResultHostFileSystemCorrupted();
|
||||
}
|
||||
|
||||
Result ConvertDatabaseCorruptedResult(Result res) {
|
||||
AMS_ASSERT(fs::ResultDatabaseCorrupted::Includes(res));
|
||||
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultInvalidAllocationTableBlock, fs::ResultInvalidRomAllocationTableBlock())
|
||||
R_CONVERT(fs::ResultInvalidKeyValueListElementIndex, fs::ResultInvalidRomKeyValueListElementIndex())
|
||||
R_CATCH_ALL() { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
AMS_ASSERT(false);
|
||||
return fs::ResultDatabaseCorrupted();
|
||||
}
|
||||
|
||||
Result ConvertRomFsResult(Result res) {
|
||||
R_TRY_CATCH(res) {
|
||||
R_CONVERT(fs::ResultUnsupportedVersion, fs::ResultUnsupportedRomVersion())
|
||||
R_CONVERT(fs::ResultNcaCorrupted, ConvertNcaCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultIntegrityVerificationStorageCorrupted, ConvertIntegrityVerificationStorageCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultBuiltInStorageCorrupted, ConvertBuiltInStorageCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultPartitionFileSystemCorrupted, ConvertPartitionFileSystemCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultFatFileSystemCorrupted, ConvertFatFileSystemCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultHostFileSystemCorrupted, ConvertHostFileSystemCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultDatabaseCorrupted, ConvertDatabaseCorruptedResult(res))
|
||||
R_CONVERT(fs::ResultNotFound, fs::ResultPathNotFound())
|
||||
R_CONVERT(fs::ResultPermissionDenied, fs::ResultTargetLocked())
|
||||
R_CONVERT(fs::ResultIncompatiblePath, fs::ResultPathNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadFile(IStorage *storage, s64 offset, void *buffer, size_t size) {
|
||||
AMS_ASSERT(storage != nullptr);
|
||||
AMS_ASSERT(offset >= 0);
|
||||
AMS_ASSERT(buffer != nullptr || size == 0);
|
||||
|
||||
return ConvertRomFsResult(storage->Read(offset, buffer, size));
|
||||
}
|
||||
|
||||
Result ReadFileHeader(IStorage *storage, RomFileSystemInformation *out) {
|
||||
AMS_ASSERT(storage != nullptr);
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
return ReadFile(storage, 0, out, sizeof(*out));
|
||||
}
|
||||
|
||||
constexpr size_t CalculateRequiredWorkingMemorySize(const RomFileSystemInformation &header) {
|
||||
const size_t needed_size = header.directory_bucket_size + header.directory_entry_size + header.file_bucket_size + header.file_entry_size;
|
||||
return util::AlignUp(needed_size, 8);
|
||||
}
|
||||
|
||||
class RomFsFile : public fsa::IFile, public impl::Newable {
|
||||
private:
|
||||
RomFsFileSystem *parent;
|
||||
s64 start;
|
||||
s64 end;
|
||||
public:
|
||||
RomFsFile(RomFsFileSystem *p, s64 s, s64 e) : parent(p), start(s), end(e) { /* ... */ }
|
||||
virtual ~RomFsFile() { /* ... */ }
|
||||
|
||||
Result VerifyArguments(size_t *out, s64 offset, void *buf, size_t size, const fs::ReadOption &option) {
|
||||
R_TRY(DryRead(out, offset, size, option, fs::OpenMode_Read));
|
||||
|
||||
AMS_ASSERT(this->GetStorage() != nullptr);
|
||||
AMS_ASSERT(offset >= 0);
|
||||
AMS_ASSERT(buf != nullptr || size == 0);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ConvertResult(Result res) const {
|
||||
return ConvertRomFsResult(res);
|
||||
}
|
||||
|
||||
s64 GetOffset() const {
|
||||
return this->start;
|
||||
}
|
||||
|
||||
s64 GetSize() const {
|
||||
return this->end - this->start;
|
||||
}
|
||||
|
||||
IStorage *GetStorage() {
|
||||
return this->parent->GetBaseStorage();
|
||||
}
|
||||
public:
|
||||
virtual Result ReadImpl(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
|
||||
size_t read_size = 0;
|
||||
R_TRY(this->VerifyArguments(std::addressof(read_size), offset, buffer, size, option));
|
||||
|
||||
R_TRY(this->ConvertResult(this->GetStorage()->Read(offset + this->start, buffer, size)));
|
||||
*out = read_size;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetSizeImpl(s64 *out) override {
|
||||
*out = this->GetSize();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result FlushImpl() override {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result WriteImpl(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileA();
|
||||
}
|
||||
|
||||
virtual Result SetSizeImpl(s64 size) override {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileA();
|
||||
}
|
||||
|
||||
virtual Result OperateRangeImpl(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
|
||||
switch (op_id) {
|
||||
case OperationId::InvalidateCache:
|
||||
case OperationId::QueryRange:
|
||||
{
|
||||
R_UNLESS(offset >= 0, fs::ResultOutOfRange());
|
||||
R_UNLESS(this->GetSize() >= 0, fs::ResultOutOfRange());
|
||||
}
|
||||
default:
|
||||
return fs::ResultUnsupportedOperationInRomFsFileB();
|
||||
}
|
||||
}
|
||||
public:
|
||||
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
||||
AMS_ABORT();
|
||||
}
|
||||
};
|
||||
|
||||
class RomFsDirectory : public fsa::IDirectory, public impl::Newable {
|
||||
private:
|
||||
using FindPosition = RomFsFileSystem::RomFileTable::FindPosition;
|
||||
private:
|
||||
RomFsFileSystem *parent;
|
||||
FindPosition current_find;
|
||||
FindPosition first_find;
|
||||
fs::OpenDirectoryMode mode;
|
||||
public:
|
||||
RomFsDirectory(RomFsFileSystem *p, const FindPosition &f, fs::OpenDirectoryMode m) : parent(p), current_find(f), first_find(f), mode(m) { /* ... */ }
|
||||
virtual ~RomFsDirectory() override { /* ... */ }
|
||||
public:
|
||||
virtual Result ReadImpl(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) {
|
||||
return this->ReadImpl(out_count, std::addressof(this->current_find), out_entries, max_entries);
|
||||
}
|
||||
|
||||
virtual Result GetEntryCountImpl(s64 *out) {
|
||||
FindPosition find = this->first_find;
|
||||
return this->ReadImpl(out, std::addressof(find), nullptr, 0);
|
||||
}
|
||||
private:
|
||||
Result ReadImpl(s64 *out_count, FindPosition *find, DirectoryEntry *out_entries, s64 max_entries) {
|
||||
AMS_ASSERT(out_count != nullptr);
|
||||
AMS_ASSERT(find != nullptr);
|
||||
|
||||
constexpr size_t NameBufferSize = fs::EntryNameLengthMax + 1;
|
||||
char *name_buf = static_cast<char *>(::ams::fs::impl::Allocate(NameBufferSize));
|
||||
R_UNLESS(name_buf != nullptr, fs::ResultAllocationFailureInRomFsFileSystemE());
|
||||
ON_SCOPE_EXIT { ::ams::fs::impl::Deallocate(name_buf, NameBufferSize); };
|
||||
|
||||
s32 i = 0;
|
||||
|
||||
if (this->mode & fs::OpenDirectoryMode_Directory) {
|
||||
while (i < max_entries || out_entries == nullptr) {
|
||||
R_TRY_CATCH(this->parent->GetRomFileTable()->FindNextDirectory(name_buf, find, NameBufferSize)) {
|
||||
R_CATCH(fs::ResultDbmFindFinished) { break; }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
if (out_entries) {
|
||||
R_UNLESS(strnlen(name_buf, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath());
|
||||
strncpy(out_entries[i].name, name_buf, fs::EntryNameLengthMax);
|
||||
out_entries[i].name[fs::EntryNameLengthMax] = '\x00';
|
||||
out_entries[i].type = fs::DirectoryEntryType_Directory;
|
||||
out_entries[i].file_size = 0;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->mode & fs::OpenDirectoryMode_File) {
|
||||
while (i < max_entries || out_entries == nullptr) {
|
||||
auto file_pos = find->next_file;
|
||||
|
||||
R_TRY_CATCH(this->parent->GetRomFileTable()->FindNextFile(name_buf, find, NameBufferSize)) {
|
||||
R_CATCH(fs::ResultDbmFindFinished) { break; }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
if (out_entries) {
|
||||
R_UNLESS(strnlen(name_buf, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath());
|
||||
strncpy(out_entries[i].name, name_buf, fs::EntryNameLengthMax);
|
||||
out_entries[i].name[fs::EntryNameLengthMax] = '\x00';
|
||||
out_entries[i].type = fs::DirectoryEntryType_File;
|
||||
|
||||
RomFsFileSystem::RomFileTable::FileInfo file_info;
|
||||
R_TRY(this->parent->GetRomFileTable()->OpenFile(std::addressof(file_info), this->parent->GetRomFileTable()->ConvertToFileId(file_pos)));
|
||||
out_entries[i].file_size = file_info.size.Get();
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
*out_count = i;
|
||||
return ResultSuccess();
|
||||
}
|
||||
public:
|
||||
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
||||
AMS_ABORT();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
RomFsFileSystem::RomFsFileSystem() : base_storage() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
RomFsFileSystem::~RomFsFileSystem() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::GetRequiredWorkingMemorySize(size_t *out, IStorage *storage) {
|
||||
RomFileSystemInformation header;
|
||||
R_TRY(ReadFileHeader(storage, std::addressof(header)));
|
||||
|
||||
*out = CalculateRequiredWorkingMemorySize(header);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::Initialize(IStorage *base, void *work, size_t work_size, bool use_cache) {
|
||||
AMS_ABORT_UNLESS(!use_cache || work != nullptr);
|
||||
AMS_ABORT_UNLESS(base != nullptr);
|
||||
|
||||
/* Read the header. */
|
||||
RomFileSystemInformation header;
|
||||
R_TRY(ReadFileHeader(base, std::addressof(header)));
|
||||
|
||||
/* Set up our storages. */
|
||||
if (use_cache) {
|
||||
const size_t needed_size = CalculateRequiredWorkingMemorySize(header);
|
||||
R_UNLESS(work_size >= needed_size, fs::ResultPreconditionViolation());
|
||||
|
||||
u8 *buf = static_cast<u8 *>(work);
|
||||
auto dir_bucket_buf = buf; buf += header.directory_bucket_size;
|
||||
auto dir_entry_buf = buf; buf += header.directory_entry_size;
|
||||
auto file_bucket_buf = buf; buf += header.file_bucket_size;
|
||||
auto file_entry_buf = buf; buf += header.file_entry_size;
|
||||
|
||||
R_TRY(ReadFile(base, header.directory_bucket_offset, dir_bucket_buf, header.directory_bucket_size));
|
||||
R_TRY(ReadFile(base, header.directory_entry_offset, dir_entry_buf, header.directory_entry_size));
|
||||
R_TRY(ReadFile(base, header.file_bucket_offset, file_bucket_buf, header.file_bucket_size));
|
||||
R_TRY(ReadFile(base, header.file_entry_offset, file_entry_buf, header.file_entry_size));
|
||||
|
||||
this->dir_bucket_storage.reset(new MemoryStorage(dir_bucket_buf, header.directory_bucket_size));
|
||||
this->dir_entry_storage.reset(new MemoryStorage(dir_entry_buf, header.directory_entry_size));
|
||||
this->file_bucket_storage.reset(new MemoryStorage(file_bucket_buf, header.file_bucket_size));
|
||||
this->file_entry_storage.reset(new MemoryStorage(file_entry_buf, header.file_entry_size));
|
||||
} else {
|
||||
this->dir_bucket_storage.reset(new SubStorage(base, header.directory_bucket_offset, header.directory_bucket_size));
|
||||
this->dir_entry_storage.reset(new SubStorage(base, header.directory_entry_offset, header.directory_entry_size));
|
||||
this->file_bucket_storage.reset(new SubStorage(base, header.file_bucket_offset, header.file_bucket_size));
|
||||
this->file_entry_storage.reset(new SubStorage(base, header.file_entry_offset, header.file_entry_size));
|
||||
}
|
||||
|
||||
/* Ensure we allocated storages successfully. */
|
||||
R_UNLESS(this->dir_bucket_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemA());
|
||||
R_UNLESS(this->dir_entry_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemA());
|
||||
R_UNLESS(this->file_bucket_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemA());
|
||||
R_UNLESS(this->file_entry_storage != nullptr, fs::ResultAllocationFailureInRomFsFileSystemA());
|
||||
|
||||
/* Initialize the rom table. */
|
||||
{
|
||||
|
||||
SubStorage db(this->dir_bucket_storage.get(), 0, header.directory_bucket_size);
|
||||
SubStorage de(this->dir_entry_storage.get(), 0, header.directory_entry_size);
|
||||
SubStorage fb(this->file_bucket_storage.get(), 0, header.file_bucket_size);
|
||||
SubStorage fe(this->file_entry_storage.get(), 0, header.file_entry_size);
|
||||
R_TRY(this->rom_file_table.Initialize(db, de, fb, fe));
|
||||
}
|
||||
|
||||
/* Set members. */
|
||||
this->entry_size = header.body_offset;
|
||||
this->base_storage = base;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::Initialize(std::unique_ptr<IStorage>&& base, void *work, size_t work_size, bool use_cache) {
|
||||
this->unique_storage = std::move(base);
|
||||
return this->Initialize(this->unique_storage.get(), work, work_size, use_cache);
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::GetFileInfo(RomFileTable::FileInfo *out, const char *path) {
|
||||
R_TRY_CATCH(this->rom_file_table.OpenFile(out, path)) {
|
||||
R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound());
|
||||
R_CONVERT(fs::ResultDbmInvalidOperation, fs::ResultPathNotFound());
|
||||
} R_END_TRY_CATCH;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
IStorage *RomFsFileSystem::GetBaseStorage() {
|
||||
return this->base_storage;
|
||||
}
|
||||
|
||||
RomFsFileSystem::RomFileTable *RomFsFileSystem::GetRomFileTable() {
|
||||
return std::addressof(this->rom_file_table);
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::GetFileBaseOffset(s64 *out, const char *path) {
|
||||
AMS_ABORT_UNLESS(out != nullptr);
|
||||
AMS_ABORT_UNLESS(path != nullptr);
|
||||
|
||||
RomFileTable::FileInfo info;
|
||||
R_TRY(this->GetFileInfo(std::addressof(info), path));
|
||||
*out = this->entry_size + info.offset.Get();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::CreateFileImpl(const char *path, s64 size, int flags) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::DeleteFileImpl(const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::CreateDirectoryImpl(const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::DeleteDirectoryImpl(const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::DeleteDirectoryRecursivelyImpl(const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::RenameFileImpl(const char *old_path, const char *new_path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::RenameDirectoryImpl(const char *old_path, const char *new_path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) {
|
||||
RomDirectoryInfo dir_info;
|
||||
R_TRY_CATCH(this->rom_file_table.GetDirectoryInformation(std::addressof(dir_info), path)) {
|
||||
R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound())
|
||||
R_CATCH(fs::ResultDbmInvalidOperation) {
|
||||
RomFileTable::FileInfo file_info;
|
||||
R_TRY(this->GetFileInfo(std::addressof(file_info), path));
|
||||
*out = fs::DirectoryEntryType_File;
|
||||
return ResultSuccess();
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out = fs::DirectoryEntryType_Directory;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::OpenFileImpl(std::unique_ptr<fs::fsa::IFile> *out_file, const char *path, fs::OpenMode mode) {
|
||||
AMS_ASSERT(out_file != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidArgument());
|
||||
|
||||
RomFileTable::FileInfo file_info;
|
||||
R_TRY(this->GetFileInfo(std::addressof(file_info), path));
|
||||
|
||||
auto file = std::make_unique<RomFsFile>(this, this->entry_size + file_info.offset.Get(), this->entry_size + file_info.offset.Get() + file_info.size.Get());
|
||||
R_UNLESS(file != nullptr, fs::ResultAllocationFailureInRomFsFileSystemB());
|
||||
|
||||
*out_file = std::move(file);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::OpenDirectoryImpl(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const char *path, fs::OpenDirectoryMode mode) {
|
||||
AMS_ASSERT(out_dir != nullptr);
|
||||
AMS_ASSERT(path != nullptr);
|
||||
|
||||
RomFileTable::FindPosition find;
|
||||
R_TRY_CATCH(this->rom_file_table.FindOpen(std::addressof(find), path)) {
|
||||
R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound())
|
||||
R_CONVERT(fs::ResultDbmInvalidOperation, fs::ResultPathNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
auto dir = std::make_unique<RomFsDirectory>(this, find, mode);
|
||||
R_UNLESS(dir != nullptr, fs::ResultAllocationFailureInRomFsFileSystemC());
|
||||
|
||||
*out_dir = std::move(dir);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::CommitImpl() {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::GetFreeSpaceSizeImpl(s64 *out, const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemC();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::GetTotalSpaceSizeImpl(s64 *out, const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemC();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::CleanDirectoryRecursivelyImpl(const char *path) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemA();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::CommitProvisionallyImpl(s64 counter) {
|
||||
return fs::ResultUnsupportedOperationInRomFsFileSystemB();
|
||||
}
|
||||
|
||||
Result RomFsFileSystem::RollbackImpl() {
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
119
libraries/libstratosphere/source/fs/fs_save_data_management.cpp
Normal file
119
libraries/libstratosphere/source/fs/fs_save_data_management.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace impl {
|
||||
|
||||
Result ReadSaveDataFileSystemExtraData(SaveDataExtraData *out, SaveDataId id) {
|
||||
return fsReadSaveDataFileSystemExtraData(out, sizeof(*out), id);
|
||||
}
|
||||
|
||||
Result ReadSaveDataFileSystemExtraData(SaveDataExtraData *out, SaveDataSpaceId space_id, SaveDataId id) {
|
||||
return fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(out, sizeof(*out), static_cast<::FsSaveDataSpaceId>(space_id), id);
|
||||
}
|
||||
|
||||
Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId space_id, SaveDataId id, const SaveDataExtraData &extra_data) {
|
||||
return fsWriteSaveDataFileSystemExtraData(std::addressof(extra_data), sizeof(extra_data), static_cast<::FsSaveDataSpaceId>(space_id), id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DisableAutoSaveDataCreation() {
|
||||
/* Use libnx binding. */
|
||||
R_ABORT_UNLESS(fsDisableAutoSaveDataCreation());
|
||||
}
|
||||
|
||||
Result CreateSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId save_id, UserId user_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) {
|
||||
const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, save_id);
|
||||
const SaveDataCreationInfo info = {
|
||||
.size = size,
|
||||
.journal_size = journal_size,
|
||||
.block_size = DefaultSaveDataBlockSize,
|
||||
.owner_id = owner_id,
|
||||
.flags = flags,
|
||||
.space_id = space_id,
|
||||
.pseudo = false,
|
||||
};
|
||||
|
||||
static_assert(sizeof(SaveDataAttribute) == sizeof(::FsSaveDataAttribute));
|
||||
static_assert(sizeof(SaveDataCreationInfo) == sizeof(::FsSaveDataCreationInfo));
|
||||
return fsCreateSaveDataFileSystemBySystemSaveDataId(reinterpret_cast<const ::FsSaveDataAttribute *>(std::addressof(attribute)), reinterpret_cast<const ::FsSaveDataCreationInfo *>(std::addressof(info)));
|
||||
}
|
||||
|
||||
Result CreateSystemSaveData(SystemSaveDataId save_id, s64 size, s64 journal_size, u32 flags) {
|
||||
return CreateSystemSaveData(SaveDataSpaceId::System, save_id, InvalidUserId, 0, size, journal_size, flags);
|
||||
}
|
||||
|
||||
Result CreateSystemSaveData(SystemSaveDataId save_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) {
|
||||
return CreateSystemSaveData(SaveDataSpaceId::System, save_id, InvalidUserId, owner_id, size, journal_size, flags);
|
||||
}
|
||||
|
||||
Result CreateSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId save_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) {
|
||||
return CreateSystemSaveData(space_id, save_id, InvalidUserId, owner_id, size, journal_size, flags);
|
||||
}
|
||||
|
||||
Result CreateSystemSaveData(SystemSaveDataId save_id, UserId user_id, s64 size, s64 journal_size, u32 flags) {
|
||||
return CreateSystemSaveData(SaveDataSpaceId::System, save_id, user_id, 0, size, journal_size, flags);
|
||||
}
|
||||
|
||||
Result CreateSystemSaveData(SystemSaveDataId save_id, UserId user_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) {
|
||||
return CreateSystemSaveData(SaveDataSpaceId::System, save_id, user_id, owner_id, size, journal_size, flags);
|
||||
}
|
||||
|
||||
Result DeleteSaveData(SaveDataId id) {
|
||||
/* TODO: Libnx binding for DeleteSaveDataFileSystem */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id) {
|
||||
return fsDeleteSaveDataFileSystemBySaveDataSpaceId(static_cast<::FsSaveDataSpaceId>(space_id), id);
|
||||
}
|
||||
|
||||
Result DeleteSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id) {
|
||||
const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, id);
|
||||
|
||||
/* TODO: Libnx binding for DeleteSaveDataFileSystemBySaveDataAttribute */
|
||||
AMS_UNUSED(attribute);
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
Result GetSaveDataFlags(u32 *out, SaveDataId id) {
|
||||
SaveDataExtraData extra_data;
|
||||
R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), id));
|
||||
|
||||
*out = extra_data.flags;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result GetSaveDataFlags(u32 *out, SaveDataSpaceId space_id, SaveDataId id) {
|
||||
SaveDataExtraData extra_data;
|
||||
R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), space_id, id));
|
||||
|
||||
*out = extra_data.flags;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SetSaveDataFlags(SaveDataId id, SaveDataSpaceId space_id, u32 flags) {
|
||||
SaveDataExtraData extra_data;
|
||||
R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), space_id, id));
|
||||
extra_data.flags = flags;
|
||||
return impl::WriteSaveDataFileSystemExtraData(space_id, id, extra_data);
|
||||
}
|
||||
|
||||
}
|
60
libraries/libstratosphere/source/fs/fs_scoped_setter.hpp
Normal file
60
libraries/libstratosphere/source/fs/fs_scoped_setter.hpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
template<typename T>
|
||||
class ScopedSetter {
|
||||
NON_COPYABLE(ScopedSetter);
|
||||
private:
|
||||
T *ptr;
|
||||
T value;
|
||||
public:
|
||||
constexpr ALWAYS_INLINE ScopedSetter(T &p, T v) : ptr(std::addressof(p)), value(v) { /* ... */ }
|
||||
ALWAYS_INLINE ~ScopedSetter() {
|
||||
if (this->ptr) {
|
||||
*this->ptr = this->value;
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ScopedSetter(ScopedSetter &&rhs) {
|
||||
this->ptr = rhs.ptr;
|
||||
this->value = rhs.value;
|
||||
rhs.Reset();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ScopedSetter &operator=(ScopedSetter &&rhs) {
|
||||
this->ptr = rhs.ptr;
|
||||
this->value = rhs.value;
|
||||
rhs.Reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void Set(T v) { this->value = v; }
|
||||
private:
|
||||
ALWAYS_INLINE void Reset() {
|
||||
this->ptr = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ALWAYS_INLINE ScopedSetter<T> MakeScopedSetter(T &p, T v) {
|
||||
return ScopedSetter<T>(p, v);
|
||||
}
|
||||
|
||||
}
|
37
libraries/libstratosphere/source/fs/fs_sd_card.cpp
Normal file
37
libraries/libstratosphere/source/fs/fs_sd_card.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
Result MountSdCard(const char *name) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountName(name));
|
||||
|
||||
/* Open the SD card. This uses libnx bindings. */
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenSdCardFileSystem(std::addressof(fs)));
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInSdCardA());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_filesystem_accessor.hpp"
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
bool IsSignedSystemPartitionOnSdCardValid(const char *system_root_path) {
|
||||
/* Get the accessor for the system filesystem. */
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_ABORT_UNLESS(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), system_root_path));
|
||||
|
||||
char is_valid;
|
||||
R_TRY_CATCH(accessor->QueryEntry(std::addressof(is_valid), 1, nullptr, 0, fsa::QueryId::IsSignedSystemPartitionOnSdCardValid, "/")) {
|
||||
/* If querying isn't supported, then the partition isn't valid. */
|
||||
R_CATCH(fs::ResultUnsupportedOperation) { is_valid = false; }
|
||||
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
bool IsSignedSystemPartitionOnSdCardValidDeprecated() {
|
||||
/* Ensure we only call with correct version. */
|
||||
auto version = hos::GetVersion();
|
||||
AMS_ABORT_UNLESS(hos::Version_400 <= version && version < hos::Version_800);
|
||||
|
||||
/* Check that the partition is valid. */
|
||||
bool is_valid;
|
||||
R_ABORT_UNLESS(fsIsSignedSystemPartitionOnSdCardValid(std::addressof(is_valid)));
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
}
|
33
libraries/libstratosphere/source/fs/fs_system_data.cpp
Normal file
33
libraries/libstratosphere/source/fs/fs_system_data.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
Result QueryMountSystemDataCacheSize(size_t *out, ncm::SystemDataId data_id) {
|
||||
return impl::QueryMountDataCacheSize(out, data_id, ncm::StorageId::BuiltInSystem);
|
||||
}
|
||||
|
||||
Result MountSystemData(const char *name, ncm::SystemDataId data_id) {
|
||||
return impl::MountData(name, data_id, ncm::StorageId::BuiltInSystem);
|
||||
}
|
||||
|
||||
Result MountSystemData(const char *name, ncm::SystemDataId data_id, void *cache_buffer, size_t cache_size) {
|
||||
return impl::MountData(name, data_id, ncm::StorageId::BuiltInSystem, cache_buffer, cache_size);
|
||||
}
|
||||
|
||||
}
|
61
libraries/libstratosphere/source/fs/fs_system_save_data.cpp
Normal file
61
libraries/libstratosphere/source/fs/fs_system_save_data.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fsa/fs_mount_utils.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
Result MountSystemSaveDataImpl(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id, SaveDataType type) {
|
||||
/* Validate the mount name. */
|
||||
R_TRY(impl::CheckMountName(name));
|
||||
|
||||
/* Create the attribute. */
|
||||
const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, type, user_id, id);
|
||||
static_assert(sizeof(attribute) == sizeof(::FsSaveDataAttribute));
|
||||
|
||||
/* Open the filesystem, use libnx bindings. */
|
||||
::FsFileSystem fs;
|
||||
R_TRY(fsOpenSaveDataFileSystemBySystemSaveDataId(std::addressof(fs), static_cast<::FsSaveDataSpaceId>(space_id), reinterpret_cast<const ::FsSaveDataAttribute *>(std::addressof(attribute))));
|
||||
|
||||
/* Allocate a new filesystem wrapper. */
|
||||
auto fsa = std::make_unique<RemoteFileSystem>(fs);
|
||||
R_UNLESS(fsa != nullptr, fs::ResultAllocationFailureInSystemSaveDataA());
|
||||
|
||||
/* Register. */
|
||||
return fsa::Register(name, std::move(fsa));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result MountSystemSaveData(const char *name, SystemSaveDataId id) {
|
||||
return MountSystemSaveData(name, id, InvalidUserId);
|
||||
}
|
||||
|
||||
Result MountSystemSaveData(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id) {
|
||||
return MountSystemSaveData(name, space_id, id, InvalidUserId);
|
||||
}
|
||||
|
||||
Result MountSystemSaveData(const char *name, SystemSaveDataId id, UserId user_id) {
|
||||
return MountSystemSaveData(name, SaveDataSpaceId::System, id, user_id);
|
||||
}
|
||||
|
||||
Result MountSystemSaveData(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id) {
|
||||
return MountSystemSaveDataImpl(name, space_id, id, user_id, SaveDataType::System);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_directory_accessor.hpp"
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
DirectoryAccessor::DirectoryAccessor(std::unique_ptr<fsa::IDirectory>&& d, FileSystemAccessor &p) : impl(std::move(d)), parent(p) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
DirectoryAccessor::~DirectoryAccessor() {
|
||||
this->impl.reset();
|
||||
this->parent.NotifyCloseDirectory(this);
|
||||
}
|
||||
|
||||
Result DirectoryAccessor::Read(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) {
|
||||
return this->impl->Read(out_count, out_entries, max_entries);
|
||||
}
|
||||
|
||||
Result DirectoryAccessor::GetEntryCount(s64 *out) {
|
||||
return this->impl->GetEntryCount(out);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
class FileSystemAccessor;
|
||||
|
||||
class DirectoryAccessor : public util::IntrusiveListBaseNode<DirectoryAccessor>, public Newable {
|
||||
NON_COPYABLE(DirectoryAccessor);
|
||||
private:
|
||||
std::unique_ptr<fsa::IDirectory> impl;
|
||||
FileSystemAccessor &parent;
|
||||
public:
|
||||
DirectoryAccessor(std::unique_ptr<fsa::IDirectory>&& d, FileSystemAccessor &p);
|
||||
~DirectoryAccessor();
|
||||
|
||||
Result Read(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries);
|
||||
Result GetEntryCount(s64 *out);
|
||||
|
||||
FileSystemAccessor &GetParent() const { return this->parent; }
|
||||
};
|
||||
|
||||
}
|
123
libraries/libstratosphere/source/fs/fsa/fs_file_accessor.cpp
Normal file
123
libraries/libstratosphere/source/fs/fsa/fs_file_accessor.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "../fs_scoped_setter.hpp"
|
||||
#include "../fs_file_path_hash.hpp"
|
||||
#include "fs_file_accessor.hpp"
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
FileAccessor::FileAccessor(std::unique_ptr<fsa::IFile>&& f, FileSystemAccessor *p, OpenMode mode)
|
||||
: impl(std::move(f)), parent(p), write_state(WriteState::None), write_result(ResultSuccess()), open_mode(mode)
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
FileAccessor::~FileAccessor() {
|
||||
/* Ensure that all files are flushed. */
|
||||
if (R_FAILED(this->write_result)) {
|
||||
AMS_ABORT_UNLESS(this->write_state != WriteState::NeedsFlush);
|
||||
}
|
||||
this->impl.reset();
|
||||
|
||||
if (this->parent != nullptr) {
|
||||
this->parent->NotifyCloseFile(this);
|
||||
}
|
||||
}
|
||||
|
||||
Result FileAccessor::ReadWithCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option, bool use_path_cache, bool use_data_cache) {
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
Result FileAccessor::ReadWithoutCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option) {
|
||||
return this->impl->Read(out, offset, buf, size, option);
|
||||
}
|
||||
|
||||
Result FileAccessor::Read(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option) {
|
||||
/* Fail after a write fails. */
|
||||
R_TRY(this->write_result);
|
||||
|
||||
/* TODO: Logging. */
|
||||
/* TODO: Support cache. */
|
||||
const bool use_path_cache = this->parent != nullptr && this->file_path_hash != nullptr;
|
||||
const bool use_data_cache = /* TODO */false && this->parent != nullptr && this->parent->IsFileDataCacheAttachable();
|
||||
|
||||
if (use_path_cache && use_data_cache && false) {
|
||||
/* TODO */
|
||||
return this->ReadWithCacheAccessLog(out, offset, buf, size, option, use_path_cache, use_data_cache);
|
||||
} else {
|
||||
return this->ReadWithoutCacheAccessLog(out, offset, buf, size, option);
|
||||
}
|
||||
}
|
||||
|
||||
Result FileAccessor::Write(s64 offset, const void *buf, size_t size, const WriteOption &option) {
|
||||
/* Fail after a write fails. */
|
||||
R_TRY(this->write_result);
|
||||
|
||||
auto setter = MakeScopedSetter(this->write_state, WriteState::Failed);
|
||||
if (this->file_path_hash != nullptr && /* TODO */ false) {
|
||||
/* TODO */
|
||||
AMS_ABORT();
|
||||
} else {
|
||||
R_TRY(this->UpdateLastResult(this->impl->Write(offset, buf, size, option)));
|
||||
}
|
||||
|
||||
setter.Set(option.HasFlushFlag() ? WriteState::None : WriteState::NeedsFlush);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileAccessor::Flush() {
|
||||
/* Fail after a write fails. */
|
||||
R_TRY(this->write_result);
|
||||
|
||||
auto setter = MakeScopedSetter(this->write_state, WriteState::Failed);
|
||||
R_TRY(this->UpdateLastResult(this->impl->Flush()));
|
||||
setter.Set(WriteState::None);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileAccessor::SetSize(s64 size) {
|
||||
/* Fail after a write fails. */
|
||||
R_TRY(this->write_result);
|
||||
|
||||
const WriteState old_write_state = this->write_state;
|
||||
auto setter = MakeScopedSetter(this->write_state, WriteState::Failed);
|
||||
|
||||
R_TRY(this->UpdateLastResult(this->impl->SetSize(size)));
|
||||
|
||||
if (this->file_path_hash != nullptr) {
|
||||
/* TODO: invalidate path cache */
|
||||
}
|
||||
|
||||
setter.Set(old_write_state);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileAccessor::GetSize(s64 *out) {
|
||||
/* Fail after a write fails. */
|
||||
R_TRY(this->write_result);
|
||||
|
||||
return this->impl->GetSize(out);
|
||||
}
|
||||
|
||||
Result FileAccessor::OperateRange(void *dst, size_t dst_size, OperationId operation, s64 offset, s64 size, const void *src, size_t src_size) {
|
||||
return this->impl->OperateRange(dst, dst_size, operation, offset, size, src, src_size);
|
||||
}
|
||||
|
||||
}
|
68
libraries/libstratosphere/source/fs/fsa/fs_file_accessor.hpp
Normal file
68
libraries/libstratosphere/source/fs/fsa/fs_file_accessor.hpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
struct FilePathHash;
|
||||
class FileSystemAccessor;
|
||||
|
||||
enum class WriteState {
|
||||
None,
|
||||
NeedsFlush,
|
||||
Failed,
|
||||
};
|
||||
|
||||
class FileAccessor : public util::IntrusiveListBaseNode<FileAccessor>, public Newable {
|
||||
NON_COPYABLE(FileAccessor);
|
||||
private:
|
||||
std::unique_ptr<fsa::IFile> impl;
|
||||
FileSystemAccessor * const parent;
|
||||
WriteState write_state;
|
||||
Result write_result;
|
||||
const OpenMode open_mode;
|
||||
std::unique_ptr<FilePathHash> file_path_hash;
|
||||
s32 path_hash_index;
|
||||
public:
|
||||
FileAccessor(std::unique_ptr<fsa::IFile>&& f, FileSystemAccessor *p, OpenMode mode);
|
||||
~FileAccessor();
|
||||
|
||||
Result Read(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option);
|
||||
Result Write(s64 offset, const void *buf, size_t size, const WriteOption &option);
|
||||
Result Flush();
|
||||
Result SetSize(s64 size);
|
||||
Result GetSize(s64 *out);
|
||||
Result OperateRange(void *dst, size_t dst_size, OperationId operation, s64 offset, s64 size, const void *src, size_t src_size);
|
||||
|
||||
OpenMode GetOpenMode() const { return this->open_mode; }
|
||||
WriteState GetWriteState() const { return this->write_state; }
|
||||
FileSystemAccessor *GetParent() const { return this->parent; }
|
||||
|
||||
void SetFilePathHash(std::unique_ptr<FilePathHash>&& file_path_hash, s32 index);
|
||||
Result ReadWithoutCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option);
|
||||
private:
|
||||
Result ReadWithCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option, bool use_path_cache, bool use_data_cache);
|
||||
|
||||
ALWAYS_INLINE Result UpdateLastResult(Result r) {
|
||||
if (!fs::ResultNotEnoughFreeSpace::Includes(r)) {
|
||||
this->write_result = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_file_accessor.hpp"
|
||||
#include "fs_directory_accessor.hpp"
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename List, typename Iter>
|
||||
void Remove(List &list, Iter *desired) {
|
||||
for (auto it = list.cbegin(); it != list.cend(); it++) {
|
||||
if (it.operator->() == desired) {
|
||||
list.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* This should never happen. */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
Result ValidatePath(const char *mount_name, const char *path) {
|
||||
const size_t mount_name_len = strnlen(mount_name, MountNameLengthMax);
|
||||
const size_t path_len = strnlen(path, EntryNameLengthMax);
|
||||
R_UNLESS(mount_name_len + 1 + path_len <= EntryNameLengthMax, fs::ResultTooLongPath());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ValidateMountName(const char *name) {
|
||||
R_UNLESS(name[0] != 0, fs::ResultInvalidMountName());
|
||||
R_UNLESS(strnlen(name, sizeof(MountName)) < sizeof(MountName), fs::ResultInvalidMountName());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename List>
|
||||
Result ValidateNoOpenWriteModeFiles(List &list) {
|
||||
for (auto it = list.cbegin(); it != list.cend(); it++) {
|
||||
R_UNLESS((it->GetOpenMode() & OpenMode_Write) == 0, fs::ResultWriteModeFileNotClosed());
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FileSystemAccessor::FileSystemAccessor(const char *n, std::unique_ptr<fsa::IFileSystem> &&fs, std::unique_ptr<fsa::ICommonMountNameGenerator> &&generator)
|
||||
: impl(std::move(fs)), mount_name_generator(std::move(generator)),
|
||||
access_log_enabled(false), data_cache_attachable(false), path_cache_attachable(false), path_cache_attached(false), multi_commit_supported(false)
|
||||
{
|
||||
R_ABORT_UNLESS(ValidateMountName(n));
|
||||
std::strncpy(this->name.str, n, MountNameLengthMax);
|
||||
this->name.str[MountNameLengthMax] = 0;
|
||||
}
|
||||
|
||||
FileSystemAccessor::~FileSystemAccessor() {
|
||||
std::scoped_lock lk(this->open_list_lock);
|
||||
|
||||
/* TODO: Iterate over list entries. */
|
||||
|
||||
if (!this->open_file_list.empty()) { R_ABORT_UNLESS(fs::ResultFileNotClosed()); }
|
||||
if (!this->open_dir_list.empty()) { R_ABORT_UNLESS(fs::ResultDirectoryNotClosed()); }
|
||||
|
||||
if (this->path_cache_attached) {
|
||||
/* TODO: Invalidate path cache */
|
||||
}
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::GetCommonMountName(char *dst, size_t dst_size) const {
|
||||
R_UNLESS(this->mount_name_generator != nullptr, fs::ResultPreconditionViolation());
|
||||
return this->mount_name_generator->GenerateCommonMountName(dst, dst_size);
|
||||
}
|
||||
|
||||
std::shared_ptr<fssrv::impl::FileSystemInterfaceAdapter> FileSystemAccessor::GetMultiCommitTarget() {
|
||||
if (this->multi_commit_supported) {
|
||||
/* TODO: Support multi commit. */
|
||||
AMS_ABORT();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FileSystemAccessor::NotifyCloseFile(FileAccessor *f) {
|
||||
std::scoped_lock lk(this->open_list_lock);
|
||||
Remove(this->open_file_list, f);
|
||||
}
|
||||
|
||||
void FileSystemAccessor::NotifyCloseDirectory(DirectoryAccessor *d) {
|
||||
std::scoped_lock lk(this->open_list_lock);
|
||||
Remove(this->open_dir_list, d);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::CreateFile(const char *path, s64 size, int option) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
if (this->path_cache_attached) {
|
||||
/* TODO: Path cache */
|
||||
R_TRY(this->impl->CreateFile(path, size, option));
|
||||
} else {
|
||||
R_TRY(this->impl->CreateFile(path, size, option));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::DeleteFile(const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->DeleteFile(path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::CreateDirectory(const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->CreateDirectory(path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::DeleteDirectory(const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->DeleteDirectory(path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::DeleteDirectoryRecursively(const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::RenameFile(const char *old_path, const char *new_path) {
|
||||
R_TRY(ValidatePath(this->name.str, old_path));
|
||||
R_TRY(ValidatePath(this->name.str, new_path));
|
||||
if (this->path_cache_attached) {
|
||||
/* TODO: Path cache */
|
||||
R_TRY(this->impl->RenameFile(old_path, new_path));
|
||||
} else {
|
||||
R_TRY(this->impl->RenameFile(old_path, new_path));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::RenameDirectory(const char *old_path, const char *new_path) {
|
||||
R_TRY(ValidatePath(this->name.str, old_path));
|
||||
R_TRY(ValidatePath(this->name.str, new_path));
|
||||
if (this->path_cache_attached) {
|
||||
/* TODO: Path cache */
|
||||
R_TRY(this->impl->RenameDirectory(old_path, new_path));
|
||||
} else {
|
||||
R_TRY(this->impl->RenameDirectory(old_path, new_path));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::GetEntryType(DirectoryEntryType *out, const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->GetEntryType(out, path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::OpenFile(std::unique_ptr<FileAccessor> *out_file, const char *path, OpenMode mode) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
|
||||
std::unique_ptr<fsa::IFile> file;
|
||||
R_TRY(this->impl->OpenFile(std::addressof(file), path, mode));
|
||||
|
||||
auto accessor = new FileAccessor(std::move(file), this, mode);
|
||||
R_UNLESS(accessor != nullptr, fs::ResultAllocationFailureInFileSystemAccessorA());
|
||||
|
||||
{
|
||||
std::scoped_lock lk(this->open_list_lock);
|
||||
this->open_file_list.push_back(*accessor);
|
||||
}
|
||||
|
||||
if (this->path_cache_attached) {
|
||||
if (mode & OpenMode_Append) {
|
||||
/* TODO: Append Path cache */
|
||||
} else {
|
||||
/* TODO: Non-append path cache */
|
||||
}
|
||||
}
|
||||
|
||||
out_file->reset(accessor);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::OpenDirectory(std::unique_ptr<DirectoryAccessor> *out_dir, const char *path, OpenDirectoryMode mode) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
|
||||
std::unique_ptr<fsa::IDirectory> dir;
|
||||
R_TRY(this->impl->OpenDirectory(std::addressof(dir), path, mode));
|
||||
|
||||
auto accessor = new DirectoryAccessor(std::move(dir), *this);
|
||||
R_UNLESS(accessor != nullptr, fs::ResultAllocationFailureInFileSystemAccessorB());
|
||||
|
||||
{
|
||||
std::scoped_lock lk(this->open_list_lock);
|
||||
this->open_dir_list.push_back(*accessor);
|
||||
}
|
||||
|
||||
out_dir->reset(accessor);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::Commit() {
|
||||
{
|
||||
std::scoped_lock lk(this->open_list_lock);
|
||||
R_ABORT_UNLESS(ValidateNoOpenWriteModeFiles(this->open_file_list));
|
||||
}
|
||||
return this->impl->Commit();
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::GetFreeSpaceSize(s64 *out, const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->GetFreeSpaceSize(out, path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::GetTotalSpaceSize(s64 *out, const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->GetTotalSpaceSize(out, path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::CleanDirectoryRecursively(const char *path) {
|
||||
R_TRY(ValidatePath(this->name.str, path));
|
||||
return this->impl->CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::GetFileTimeStampRaw(FileTimeStampRaw *out, const char *path) {
|
||||
return this->impl->GetFileTimeStampRaw(out, path);
|
||||
}
|
||||
|
||||
Result FileSystemAccessor::QueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fsa::QueryId query, const char *path) {
|
||||
return this->impl->QueryEntry(dst, dst_size, src, src_size, query, path);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/fssrv/fssrv_interface_adapters.hpp>
|
||||
#include "fs_mount_name.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
class FileAccessor;
|
||||
class DirectoryAccessor;
|
||||
|
||||
class FileSystemAccessor : public util::IntrusiveListBaseNode<FileSystemAccessor>, public Newable {
|
||||
NON_COPYABLE(FileSystemAccessor);
|
||||
friend class FileAccessor;
|
||||
friend class DirectoryAccessor;
|
||||
private:
|
||||
using FileList = util::IntrusiveListBaseTraits<FileAccessor>::ListType;
|
||||
using DirList = util::IntrusiveListBaseTraits<DirectoryAccessor>::ListType;
|
||||
private:
|
||||
MountName name;
|
||||
std::unique_ptr<fsa::IFileSystem> impl;
|
||||
FileList open_file_list;
|
||||
DirList open_dir_list;
|
||||
os::Mutex open_list_lock;
|
||||
std::unique_ptr<fsa::ICommonMountNameGenerator> mount_name_generator;
|
||||
bool access_log_enabled;
|
||||
bool data_cache_attachable;
|
||||
bool path_cache_attachable;
|
||||
bool path_cache_attached;
|
||||
bool multi_commit_supported;
|
||||
public:
|
||||
FileSystemAccessor(const char *name, std::unique_ptr<fsa::IFileSystem> &&fs, std::unique_ptr<fsa::ICommonMountNameGenerator> &&generator = nullptr);
|
||||
virtual ~FileSystemAccessor();
|
||||
|
||||
Result CreateFile(const char *path, s64 size, int option);
|
||||
Result DeleteFile(const char *path);
|
||||
Result CreateDirectory(const char *path);
|
||||
Result DeleteDirectory(const char *path);
|
||||
Result DeleteDirectoryRecursively(const char *path);
|
||||
Result RenameFile(const char *old_path, const char *new_path);
|
||||
Result RenameDirectory(const char *old_path, const char *new_path);
|
||||
Result GetEntryType(DirectoryEntryType *out, const char *path);
|
||||
Result OpenFile(std::unique_ptr<FileAccessor> *out_file, const char *path, OpenMode mode);
|
||||
Result OpenDirectory(std::unique_ptr<DirectoryAccessor> *out_dir, const char *path, OpenDirectoryMode mode);
|
||||
Result Commit();
|
||||
Result GetFreeSpaceSize(s64 *out, const char *path);
|
||||
Result GetTotalSpaceSize(s64 *out, const char *path);
|
||||
Result CleanDirectoryRecursively(const char *path);
|
||||
Result GetFileTimeStampRaw(FileTimeStampRaw *out, const char *path);
|
||||
Result QueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fsa::QueryId query, const char *path);
|
||||
|
||||
const char *GetName() const { return this->name.str; }
|
||||
Result GetCommonMountName(char *dst, size_t dst_size) const;
|
||||
|
||||
void SetAccessLogEnabled(bool en) { this->access_log_enabled = en; }
|
||||
void SetFileDataCacheAttachable(bool en) { this->data_cache_attachable = en; }
|
||||
void SetPathBasedFileDataCacheAttachable(bool en) { this->path_cache_attachable = en; }
|
||||
void SetMultiCommitSupported(bool en) { this->multi_commit_supported = en; }
|
||||
|
||||
bool IsAccessLogEnabled() const { return this->access_log_enabled; }
|
||||
bool IsFileDataCacheAttachable() const { return this->data_cache_attachable; }
|
||||
bool IsPathBasedFileDataCacheAttachable() const { return this->path_cache_attachable; }
|
||||
|
||||
void AttachPathBasedFileDataCache() {
|
||||
if (this->IsPathBasedFileDataCacheAttachable()) {
|
||||
this->path_cache_attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DetachPathBasedFileDataCache() {
|
||||
this->path_cache_attached = false;
|
||||
}
|
||||
|
||||
std::shared_ptr<fssrv::impl::FileSystemInterfaceAdapter> GetMultiCommitTarget();
|
||||
private:
|
||||
void NotifyCloseFile(FileAccessor *f);
|
||||
void NotifyCloseDirectory(DirectoryAccessor *d);
|
||||
};
|
||||
|
||||
}
|
26
libraries/libstratosphere/source/fs/fsa/fs_mount_name.hpp
Normal file
26
libraries/libstratosphere/source/fs/fsa/fs_mount_name.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
struct MountName {
|
||||
char str[MountNameLengthMax + 1];
|
||||
};
|
||||
static_assert(std::is_pod<MountName>::value);
|
||||
|
||||
}
|
75
libraries/libstratosphere/source/fs/fsa/fs_mount_table.cpp
Normal file
75
libraries/libstratosphere/source/fs/fsa/fs_mount_table.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_mount_table.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
bool MatchesName(const FileSystemAccessor &accessor, const char *name) {
|
||||
return std::strncmp(accessor.GetName(), name, sizeof(MountName)) == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool MountTable::CanAcceptMountName(const char *name) {
|
||||
for (const auto &fs : this->fs_list) {
|
||||
if (MatchesName(fs, name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result MountTable::Mount(std::unique_ptr<FileSystemAccessor> &&fs) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
R_UNLESS(this->CanAcceptMountName(fs->GetName()), fs::ResultMountNameAlreadyExists());
|
||||
|
||||
this->fs_list.push_back(*fs.release());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result MountTable::Find(FileSystemAccessor **out, const char *name) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
for (auto &fs : this->fs_list) {
|
||||
if (MatchesName(fs, name)) {
|
||||
*out = std::addressof(fs);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return fs::ResultNotMounted();
|
||||
}
|
||||
|
||||
void MountTable::Unmount(const char *name) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
for (auto it = this->fs_list.cbegin(); it != this->fs_list.cend(); it++) {
|
||||
if (MatchesName(*it, name)) {
|
||||
auto p = std::addressof(*it);
|
||||
this->fs_list.erase(it);
|
||||
delete p;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
R_ABORT_UNLESS(fs::ResultNotMounted());
|
||||
}
|
||||
|
||||
}
|
40
libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp
Normal file
40
libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
class MountTable : public Newable {
|
||||
NON_COPYABLE(MountTable);
|
||||
NON_MOVEABLE(MountTable);
|
||||
private:
|
||||
using FileSystemList = util::IntrusiveListBaseTraits<FileSystemAccessor>::ListType;
|
||||
private:
|
||||
FileSystemList fs_list;
|
||||
os::Mutex mutex;
|
||||
public:
|
||||
constexpr MountTable() : fs_list(), mutex() { /* ... */ }
|
||||
private:
|
||||
bool CanAcceptMountName(const char *name);
|
||||
public:
|
||||
Result Mount(std::unique_ptr<FileSystemAccessor> &&fs);
|
||||
Result Find(FileSystemAccessor **out, const char *name);
|
||||
void Unmount(const char *name);
|
||||
};
|
||||
|
||||
}
|
168
libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp
Normal file
168
libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
#include "fs_mount_utils.hpp"
|
||||
#include "fs_user_mount_table.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
const char *FindMountNameDriveSeparator(const char *path) {
|
||||
for (const char *cur = path; cur < path + MountNameLengthMax + 1; cur++) {
|
||||
if (PathTool::IsDriveSeparator(*cur)) {
|
||||
return cur;
|
||||
} else if (PathTool::IsNullTerminator(*cur)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Result GetMountNameAndSubPath(MountName *out_mount_name, const char **out_sub_path, const char *path) {
|
||||
/* Handle the Host-path case. */
|
||||
if (PathTool::IsWindowsAbsolutePath(path) || PathTool::IsUnc(path)) {
|
||||
std::strncpy(out_mount_name->str, HostRootFileSystemMountName, MountNameLengthMax);
|
||||
out_mount_name->str[MountNameLengthMax] = '\x00';
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Locate the drive separator. */
|
||||
const char *drive_separator = FindMountNameDriveSeparator(path);
|
||||
R_UNLESS(drive_separator != nullptr, fs::ResultInvalidMountName());
|
||||
|
||||
/* Ensure the mount name isn't too long. */
|
||||
const size_t len = drive_separator - path;
|
||||
R_UNLESS(len <= MountNameLengthMax, fs::ResultInvalidMountName());
|
||||
|
||||
/* Ensure the result sub-path is valid. */
|
||||
const char *sub_path = drive_separator + 1;
|
||||
R_UNLESS(!PathTool::IsNullTerminator(sub_path[0]), fs::ResultInvalidMountName());
|
||||
R_UNLESS(PathTool::IsAnySeparator(sub_path[0]), fs::ResultInvalidPathFormat());
|
||||
|
||||
/* Set output. */
|
||||
std::memcpy(out_mount_name->str, path, len);
|
||||
out_mount_name->str[len] = StringTraits::NullTerminator;
|
||||
*out_sub_path = sub_path;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IsValidMountName(const char *name) {
|
||||
if (PathTool::IsNullTerminator(*name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PathTool::IsWindowsDriveCharacter(name[0]) && PathTool::IsNullTerminator(name[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
for (const char *cur = name; !PathTool::IsNullTerminator(*cur); cur++) {
|
||||
if (PathTool::IsDriveSeparator(*cur) || PathTool::IsSeparator(*cur)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((++len) > MountNameLengthMax) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: N validates that the mount name decodes via utf-8 here. */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsWindowsDrive(const char *name) {
|
||||
return PathTool::IsWindowsAbsolutePath(name);
|
||||
}
|
||||
|
||||
bool IsReservedMountName(const char *name) {
|
||||
return name[0] == ReservedMountNamePrefixCharacter;
|
||||
}
|
||||
|
||||
Result CheckMountName(const char *name) {
|
||||
R_TRY(CheckMountNameAllowingReserved(name));
|
||||
R_UNLESS(!impl::IsReservedMountName(name), fs::ResultInvalidMountName());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result CheckMountNameAllowingReserved(const char *name) {
|
||||
R_UNLESS(name != nullptr, fs::ResultInvalidMountName());
|
||||
R_UNLESS(impl::IsValidMountName(name), fs::ResultInvalidMountName());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result FindFileSystem(FileSystemAccessor **out_accessor, const char **out_sub_path, const char *path) {
|
||||
R_UNLESS(out_accessor != nullptr, fs::ResultUnexpectedInFindFileSystemA());
|
||||
R_UNLESS(out_sub_path != nullptr, fs::ResultUnexpectedInFindFileSystemA());
|
||||
R_UNLESS(path != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
R_UNLESS(strncmp(path, HostRootFileSystemMountName, strnlen(HostRootFileSystemMountName, sizeof(MountName))) != 0, fs::ResultNotMounted());
|
||||
|
||||
MountName mount_name;
|
||||
R_TRY(GetMountNameAndSubPath(std::addressof(mount_name), out_sub_path, path));
|
||||
|
||||
return impl::Find(out_accessor, mount_name.str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
Result UnmountImpl(const char *name) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
R_TRY(impl::Find(std::addressof(accessor), name));
|
||||
|
||||
if (accessor->IsFileDataCacheAttachable()) {
|
||||
/* TODO: Data cache purge */
|
||||
}
|
||||
|
||||
impl::Unregister(name);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *src) {
|
||||
/* Ensure neither argument is nullptr. */
|
||||
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
|
||||
R_UNLESS(src != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
/* Get the mount name and sub path for the path. */
|
||||
MountName mount_name;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::GetMountNameAndSubPath(std::addressof(mount_name), std::addressof(sub_path), src));
|
||||
|
||||
impl::FileSystemAccessor *accessor;
|
||||
R_TRY(impl::Find(std::addressof(accessor), mount_name.str));
|
||||
R_TRY(accessor->GetCommonMountName(dst, dst_size));
|
||||
|
||||
const auto mount_name_len = strnlen(dst, dst_size);
|
||||
const auto common_path_len = std::snprintf(dst + mount_name_len, dst_size - mount_name_len, "%s", sub_path);
|
||||
|
||||
R_UNLESS(static_cast<size_t>(common_path_len) < dst_size - mount_name_len, fs::ResultTooLongPath());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void Unmount(const char *mount_name) {
|
||||
R_ABORT_UNLESS(UnmountImpl(mount_name));
|
||||
}
|
||||
|
||||
}
|
30
libraries/libstratosphere/source/fs/fsa/fs_mount_utils.hpp
Normal file
30
libraries/libstratosphere/source/fs/fsa/fs_mount_utils.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
class FileSystemAccessor;
|
||||
|
||||
Result FindFileSystem(FileSystemAccessor **out_accessor, const char **out_sub_path, const char *path);
|
||||
|
||||
bool IsWindowsDrive(const char *name);
|
||||
bool IsReservedMountName(const char *name);
|
||||
Result CheckMountName(const char *name);
|
||||
Result CheckMountNameAllowingReserved(const char *name);
|
||||
|
||||
}
|
51
libraries/libstratosphere/source/fs/fsa/fs_registrar.cpp
Normal file
51
libraries/libstratosphere/source/fs/fsa/fs_registrar.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
#include "fs_user_mount_table.hpp"
|
||||
|
||||
namespace ams::fs::fsa {
|
||||
|
||||
Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs) {
|
||||
auto accessor = std::make_unique<impl::FileSystemAccessor>(name, std::move(fs));
|
||||
R_UNLESS(accessor != nullptr, fs::ResultAllocationFailureInRegisterA());
|
||||
|
||||
return impl::Register(std::move(accessor));
|
||||
}
|
||||
|
||||
Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs, std::unique_ptr<ICommonMountNameGenerator> &&generator) {
|
||||
auto accessor = std::make_unique<impl::FileSystemAccessor>(name, std::move(fs), std::move(generator));
|
||||
R_UNLESS(accessor != nullptr, fs::ResultAllocationFailureInRegisterB());
|
||||
|
||||
return impl::Register(std::move(accessor));
|
||||
}
|
||||
|
||||
Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs, std::unique_ptr<ICommonMountNameGenerator> &&generator, bool use_data_cache, bool use_path_cache, bool support_multi_commit) {
|
||||
auto accessor = std::make_unique<impl::FileSystemAccessor>(name, std::move(fs), std::move(generator));
|
||||
R_UNLESS(accessor != nullptr, fs::ResultAllocationFailureInRegisterB());
|
||||
|
||||
accessor->SetFileDataCacheAttachable(use_data_cache);
|
||||
accessor->SetPathBasedFileDataCacheAttachable(use_path_cache);
|
||||
accessor->SetMultiCommitSupported(support_multi_commit);
|
||||
|
||||
return impl::Register(std::move(accessor));
|
||||
}
|
||||
|
||||
void Unregister(const char *name) {
|
||||
impl::Unregister(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_directory_accessor.hpp"
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE impl::DirectoryAccessor *Get(DirectoryHandle handle) {
|
||||
return reinterpret_cast<impl::DirectoryAccessor *>(handle.handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ReadDirectory(s64 *out_count, DirectoryEntry *out_entries, DirectoryHandle handle, s64 max_entries) {
|
||||
return Get(handle)->Read(out_count, out_entries, max_entries);
|
||||
}
|
||||
|
||||
Result GetDirectoryEntryCount(s64 *out, DirectoryHandle handle) {
|
||||
return Get(handle)->GetEntryCount(out);
|
||||
}
|
||||
|
||||
void CloseDirectory(DirectoryHandle handle) {
|
||||
delete Get(handle);
|
||||
}
|
||||
|
||||
}
|
77
libraries/libstratosphere/source/fs/fsa/fs_user_file.cpp
Normal file
77
libraries/libstratosphere/source/fs/fsa/fs_user_file.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_file_accessor.hpp"
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
ALWAYS_INLINE impl::FileAccessor *Get(FileHandle handle) {
|
||||
return reinterpret_cast<impl::FileAccessor *>(handle.handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) {
|
||||
size_t read_size;
|
||||
R_TRY(ReadFile(std::addressof(read_size), handle, offset, buffer, size, option));
|
||||
R_UNLESS(read_size == size, fs::ResultOutOfRange());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size) {
|
||||
return ReadFile(handle, offset, buffer, size, ReadOption());
|
||||
}
|
||||
|
||||
Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) {
|
||||
return Get(handle)->Read(out, offset, buffer, size, option);
|
||||
}
|
||||
|
||||
Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size) {
|
||||
return ReadFile(out, handle, offset, buffer, size, ReadOption());
|
||||
}
|
||||
|
||||
Result GetFileSize(s64 *out, FileHandle handle) {
|
||||
return Get(handle)->GetSize(out);
|
||||
}
|
||||
|
||||
Result FlushFile(FileHandle handle) {
|
||||
return Get(handle)->Flush();
|
||||
}
|
||||
|
||||
Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) {
|
||||
return Get(handle)->Write(offset, buffer, size, option);
|
||||
}
|
||||
|
||||
Result SetFileSize(FileHandle handle, s64 size) {
|
||||
return Get(handle)->SetSize(size);
|
||||
}
|
||||
|
||||
int GetFileOpenMode(FileHandle handle) {
|
||||
return Get(handle)->GetOpenMode();
|
||||
}
|
||||
|
||||
void CloseFile(FileHandle handle) {
|
||||
delete Get(handle);
|
||||
}
|
||||
|
||||
Result QueryRange(QueryRangeInfo *out, FileHandle handle, s64 offset, s64 size) {
|
||||
return Get(handle)->OperateRange(out, sizeof(*out), OperationId::QueryRange, offset, size, nullptr, 0);
|
||||
}
|
||||
|
||||
}
|
199
libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp
Normal file
199
libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
#include "fs_file_accessor.hpp"
|
||||
#include "fs_directory_accessor.hpp"
|
||||
#include "fs_mount_utils.hpp"
|
||||
#include "fs_user_mount_table.hpp"
|
||||
|
||||
namespace ams::fs {
|
||||
|
||||
Result CreateFile(const char *path, s64 size) {
|
||||
return CreateFile(path, size, 0);
|
||||
}
|
||||
|
||||
Result CreateFile(const char* path, s64 size, int option) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->CreateFile(sub_path, size, option);
|
||||
}
|
||||
|
||||
Result DeleteFile(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->DeleteFile(sub_path);
|
||||
}
|
||||
|
||||
Result CreateDirectory(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->CreateDirectory(sub_path);
|
||||
}
|
||||
|
||||
Result DeleteDirectory(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->DeleteDirectory(sub_path);
|
||||
}
|
||||
|
||||
Result DeleteDirectoryRecursively(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->DeleteDirectoryRecursively(sub_path);
|
||||
}
|
||||
|
||||
Result RenameFile(const char *old_path, const char *new_path) {
|
||||
impl::FileSystemAccessor *old_accessor;
|
||||
impl::FileSystemAccessor *new_accessor;
|
||||
const char *old_sub_path;
|
||||
const char *new_sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(old_accessor), std::addressof(old_sub_path), old_path));
|
||||
R_TRY(impl::FindFileSystem(std::addressof(new_accessor), std::addressof(new_sub_path), new_path));
|
||||
|
||||
R_UNLESS(old_accessor == new_accessor, fs::ResultRenameToOtherFileSystem());
|
||||
return old_accessor->RenameFile(old_sub_path, new_sub_path);
|
||||
}
|
||||
|
||||
Result RenameDirectory(const char *old_path, const char *new_path) {
|
||||
impl::FileSystemAccessor *old_accessor;
|
||||
impl::FileSystemAccessor *new_accessor;
|
||||
const char *old_sub_path;
|
||||
const char *new_sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(old_accessor), std::addressof(old_sub_path), old_path));
|
||||
R_TRY(impl::FindFileSystem(std::addressof(new_accessor), std::addressof(new_sub_path), new_path));
|
||||
|
||||
R_UNLESS(old_accessor == new_accessor, fs::ResultRenameToOtherFileSystem());
|
||||
return old_accessor->RenameDirectory(old_sub_path, new_sub_path);
|
||||
}
|
||||
|
||||
Result GetEntryType(DirectoryEntryType *out, const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->GetEntryType(out, sub_path);
|
||||
}
|
||||
|
||||
Result OpenFile(FileHandle *out_file, const char *path, int mode) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
R_UNLESS(out_file != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
std::unique_ptr<impl::FileAccessor> file_accessor;
|
||||
R_TRY(accessor->OpenFile(std::addressof(file_accessor), sub_path, static_cast<OpenMode>(mode)));
|
||||
|
||||
out_file->handle = file_accessor.release();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OpenDirectory(DirectoryHandle *out_dir, const char *path, int mode) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
R_UNLESS(out_dir != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
std::unique_ptr<impl::DirectoryAccessor> dir_accessor;
|
||||
R_TRY(accessor->OpenDirectory(std::addressof(dir_accessor), sub_path, static_cast<OpenDirectoryMode>(mode)));
|
||||
|
||||
out_dir->handle = dir_accessor.release();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result CleanDirectoryRecursively(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->CleanDirectoryRecursively(sub_path);
|
||||
}
|
||||
|
||||
Result GetFreeSpaceSize(s64 *out, const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->GetFreeSpaceSize(out, sub_path);
|
||||
}
|
||||
|
||||
Result GetTotalSpaceSize(s64 *out, const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->GetTotalSpaceSize(out, sub_path);
|
||||
}
|
||||
|
||||
Result SetConcatenationFileAttribute(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->QueryEntry(nullptr, 0, nullptr, 0, fsa::QueryId::SetConcatenationFileAttribute, sub_path);
|
||||
}
|
||||
|
||||
Result GetFileTimeStampRaw(FileTimeStampRaw *out, const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
const char *sub_path;
|
||||
R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path));
|
||||
|
||||
return accessor->GetFileTimeStampRaw(out, sub_path);
|
||||
}
|
||||
|
||||
Result OpenFile(FileHandle *out, std::unique_ptr<fsa::IFile> &&file, int mode) {
|
||||
R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
|
||||
|
||||
auto file_accessor = std::make_unique<impl::FileAccessor>(std::move(file), nullptr, static_cast<OpenMode>(mode));
|
||||
R_UNLESS(file_accessor != nullptr, fs::ResultAllocationFailureInNew());
|
||||
out->handle = file_accessor.release();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Result CommitImpl(const char *path) {
|
||||
impl::FileSystemAccessor *accessor;
|
||||
R_TRY(impl::Find(std::addressof(accessor), path));
|
||||
|
||||
return accessor->Commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result Commit(const char *path) {
|
||||
return CommitImpl(path);
|
||||
}
|
||||
|
||||
Result CommitSaveData(const char *path) {
|
||||
return CommitImpl(path);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fs_user_mount_table.hpp"
|
||||
#include "fs_mount_table.hpp"
|
||||
#include "fs_filesystem_accessor.hpp"
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
MountTable g_mount_table;
|
||||
|
||||
}
|
||||
|
||||
Result Register(std::unique_ptr<FileSystemAccessor> &&fs) {
|
||||
return g_mount_table.Mount(std::move(fs));
|
||||
}
|
||||
|
||||
Result Find(FileSystemAccessor **out, const char *name) {
|
||||
return g_mount_table.Find(out, name);
|
||||
}
|
||||
|
||||
void Unregister(const char *name) {
|
||||
g_mount_table.Unmount(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fs::impl {
|
||||
|
||||
class FileSystemAccessor;
|
||||
|
||||
Result Register(std::unique_ptr<FileSystemAccessor> &&fs);
|
||||
Result Find(FileSystemAccessor **out, const char *name);
|
||||
void Unregister(const char *name);
|
||||
|
||||
}
|
|
@ -178,7 +178,7 @@ namespace ams::fssystem {
|
|||
|
||||
Result DirectorySaveDataFileSystem::CopySaveFromFileSystem(fs::fsa::IFileSystem *save_fs) {
|
||||
/* If the input save is null, there's nothing to copy. */
|
||||
R_UNLESS(save_fs != nullptr, ResultSuccess());
|
||||
R_SUCCEED_IF(save_fs == nullptr);
|
||||
|
||||
/* Get a work buffer to work with. */
|
||||
std::unique_ptr<u8[]> work_buf;
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace ams::hid {
|
|||
Result EnsureHidInitialized() {
|
||||
if (!g_initialized_hid) {
|
||||
if (!serviceIsActive(hidGetServiceSession())) {
|
||||
if (!pm::info::HasLaunchedProgram(ncm::ProgramId::Hid)) {
|
||||
if (!pm::info::HasLaunchedProgram(ncm::SystemProgramId::Hid)) {
|
||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
||||
}
|
||||
InitializeHid();
|
||||
|
|
|
@ -180,11 +180,9 @@ namespace ams::kvdb {
|
|||
|
||||
Result FileKeyValueStore::InitializeWithCache(const char *dir, void *cache_buffer, size_t cache_buffer_size, size_t cache_capacity) {
|
||||
/* Ensure that the passed path is a directory. */
|
||||
{
|
||||
struct stat st;
|
||||
R_UNLESS(stat(dir, &st) == 0, fs::ResultPathNotFound());
|
||||
R_UNLESS((S_ISDIR(st.st_mode)), fs::ResultPathNotFound());
|
||||
}
|
||||
fs::DirectoryEntryType entry_type;
|
||||
R_TRY(fs::GetEntryType(std::addressof(entry_type), dir));
|
||||
R_UNLESS(entry_type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound());
|
||||
|
||||
/* Set path. */
|
||||
this->dir_path.Set(dir);
|
||||
|
@ -210,24 +208,22 @@ namespace ams::kvdb {
|
|||
}
|
||||
|
||||
/* Open the value file. */
|
||||
FILE *fp = fopen(this->GetPath(key, key_size), "rb");
|
||||
if (fp == nullptr) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(fs::OpenFile(std::addressof(file), this->GetPath(key, key_size), fs::OpenMode_Read)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound());
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Get the value size. */
|
||||
fseek(fp, 0, SEEK_END);
|
||||
const size_t value_size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
/* Ensure there's enough space for the value. */
|
||||
R_UNLESS(value_size <= max_out_size, ResultBufferInsufficient());
|
||||
R_UNLESS(file_size <= static_cast<s64>(max_out_size), ResultBufferInsufficient());
|
||||
|
||||
/* Read the value. */
|
||||
R_UNLESS(fread(out_value, value_size, 1, fp) == 1, fsdevGetLastResult());
|
||||
const size_t value_size = static_cast<size_t>(value_size);
|
||||
R_TRY(fs::ReadFile(file, 0, out_value, value_size));
|
||||
*out_size = value_size;
|
||||
|
||||
/* Cache the newly read value. */
|
||||
|
@ -251,17 +247,17 @@ namespace ams::kvdb {
|
|||
}
|
||||
|
||||
/* Open the value file. */
|
||||
FILE *fp = fopen(this->GetPath(key, key_size), "rb");
|
||||
if (fp == nullptr) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(fs::OpenFile(std::addressof(file), this->GetPath(key, key_size), fs::OpenMode_Read)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound());
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Get the value size. */
|
||||
fseek(fp, 0, SEEK_END);
|
||||
*out_size = ftell(fp);
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
*out_size = static_cast<size_t>(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
|
@ -278,18 +274,18 @@ namespace ams::kvdb {
|
|||
|
||||
/* Delete the file, if it exists. Don't check result, since it's okay if it's already deleted. */
|
||||
auto key_path = this->GetPath(key, key_size);
|
||||
std::remove(key_path);
|
||||
fs::DeleteFile(key_path);
|
||||
|
||||
/* Create the new value file. */
|
||||
R_TRY(fs::CreateFile(key_path, value_size));
|
||||
|
||||
/* Open the value file. */
|
||||
FILE *fp = fopen(key_path, "wb");
|
||||
R_UNLESS(fp != nullptr, fsdevGetLastResult());
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), key_path, fs::OpenMode_Write));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Write the value file. */
|
||||
R_UNLESS(fwrite(value, value_size, 1, fp) == 1, fsdevGetLastResult());
|
||||
|
||||
/* Flush the value file. */
|
||||
fflush(fp);
|
||||
/* Write the value file and flush. */
|
||||
R_TRY(fs::WriteFile(file, 0, value, value_size, fs::WriteOption::Flush));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -306,11 +302,9 @@ namespace ams::kvdb {
|
|||
}
|
||||
|
||||
/* Remove the file. */
|
||||
if (std::remove(this->GetPath(key, key_size)) != 0) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
R_TRY_CATCH(fs::DeleteFile(this->GetPath(key, key_size))) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultKeyNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_add_on_content_location_resolver_impl.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
Result AddOnContentLocationResolverImpl::ResolveAddOnContentPath(sf::Out<Path> out, ncm::DataId id) {
|
||||
/* Find a storage that contains the given program id. */
|
||||
ncm::StorageId storage_id = ncm::StorageId::None;
|
||||
R_UNLESS(this->registered_storages.Find(&storage_id, id), lr::ResultAddOnContentNotFound());
|
||||
|
||||
/* Obtain a content meta database for the storage id. */
|
||||
ncm::ContentMetaDatabase content_meta_database;
|
||||
R_TRY(ncm::OpenContentMetaDatabase(&content_meta_database, storage_id));
|
||||
|
||||
/* Find the latest data content id for the given program id. */
|
||||
ncm::ContentId data_content_id;
|
||||
R_TRY(content_meta_database.GetLatestData(&data_content_id, id));
|
||||
|
||||
/* Obtain a content storage for the storage id. */
|
||||
ncm::ContentStorage content_storage;
|
||||
R_TRY(ncm::OpenContentStorage(&content_storage, storage_id));
|
||||
|
||||
/* Get the path of the data content. */
|
||||
static_assert(sizeof(lr::Path) == sizeof(ncm::Path));
|
||||
content_storage.GetPath(reinterpret_cast<ncm::Path *>(out.GetPointer()), data_content_id);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverImpl::RegisterAddOnContentStorageDeprecated(ncm::DataId id, ncm::StorageId storage_id) {
|
||||
/* Register storage for the given program id. 2.0.0-8.1.0 did not require an owner application id. */
|
||||
R_UNLESS(this->registered_storages.Register(id, storage_id, ncm::InvalidApplicationId), lr::ResultTooManyRegisteredPaths());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverImpl::RegisterAddOnContentStorage(ncm::DataId id, ncm::ApplicationId application_id, ncm::StorageId storage_id) {
|
||||
/* Register storage for the given program id and owner application. */
|
||||
R_UNLESS(this->registered_storages.Register(id, storage_id, application_id), lr::ResultTooManyRegisteredPaths());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverImpl::UnregisterAllAddOnContentPath() {
|
||||
this->registered_storages.Clear();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverImpl::RefreshApplicationAddOnContent(const sf::InArray<ncm::ApplicationId> &ids) {
|
||||
if (ids.GetSize() == 0) {
|
||||
/* Clear all registered storages. */
|
||||
this->registered_storages.Clear();
|
||||
} else {
|
||||
/* Clear all registered storages excluding the provided program ids. */
|
||||
this->registered_storages.ClearExcluding(reinterpret_cast<const ncm::ProgramId *>(ids.GetPointer()), ids.GetSize());
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverImpl::UnregisterApplicationAddOnContent(ncm::ApplicationId id) {
|
||||
/* Remove entries belonging to the provided application. */
|
||||
this->registered_storages.UnregisterOwnerProgram(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_location_redirector.hpp"
|
||||
#include "lr_registered_data.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class AddOnContentLocationResolverImpl : public IAddOnContentLocationResolver {
|
||||
private:
|
||||
/* Storage for RegisteredData entries by data id. */
|
||||
RegisteredStorages<ncm::DataId, 0x800> registered_storages;
|
||||
public:
|
||||
AddOnContentLocationResolverImpl() : registered_storages(hos::GetVersion() < hos::Version_900 ? 0x800 : 0x2) { /* ... */ }
|
||||
|
||||
/* Actual commands. */
|
||||
virtual Result ResolveAddOnContentPath(sf::Out<Path> out, ncm::DataId id) override;
|
||||
virtual Result RegisterAddOnContentStorageDeprecated(ncm::DataId id, ncm::StorageId storage_id) override;
|
||||
virtual Result RegisterAddOnContentStorage(ncm::DataId id, ncm::ApplicationId application_id, ncm::StorageId storage_id) override;
|
||||
virtual Result UnregisterAllAddOnContentPath() override;
|
||||
virtual Result RefreshApplicationAddOnContent(const sf::InArray<ncm::ApplicationId> &ids) override;
|
||||
virtual Result UnregisterApplicationAddOnContent(ncm::ApplicationId id) override;
|
||||
};
|
||||
|
||||
}
|
66
libraries/libstratosphere/source/lr/lr_api.cpp
Normal file
66
libraries/libstratosphere/source/lr/lr_api.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_remote_location_resolver_impl.hpp"
|
||||
#include "lr_remote_registered_location_resolver_impl.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
namespace {
|
||||
|
||||
bool g_initialized;
|
||||
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
AMS_ASSERT(!g_initialized);
|
||||
R_ABORT_UNLESS(lrInitialize());
|
||||
g_initialized = true;
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
AMS_ASSERT(g_initialized);
|
||||
lrExit();
|
||||
g_initialized = false;
|
||||
}
|
||||
|
||||
|
||||
Result OpenLocationResolver(LocationResolver *out, ncm::StorageId storage_id) {
|
||||
LrLocationResolver lr;
|
||||
R_TRY(lrOpenLocationResolver(static_cast<NcmStorageId>(storage_id), std::addressof(lr)));
|
||||
|
||||
*out = LocationResolver(std::make_shared<RemoteLocationResolverImpl>(lr));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OpenRegisteredLocationResolver(RegisteredLocationResolver *out) {
|
||||
LrRegisteredLocationResolver lr;
|
||||
R_TRY(lrOpenRegisteredLocationResolver(std::addressof(lr)));
|
||||
|
||||
*out = RegisteredLocationResolver(std::make_shared<RemoteRegisteredLocationResolverImpl>(lr));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OpenAddOnContentLocationResolver(AddOnContentLocationResolver *out) {
|
||||
/* TODO: libnx binding */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
Result RefreshLocationResolver(ncm::StorageId storage_id) {
|
||||
/* TODO: libnx binding */
|
||||
AMS_ABORT();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_content_location_resolver_impl.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
ContentLocationResolverImpl::~ContentLocationResolverImpl() {
|
||||
this->ClearRedirections();
|
||||
}
|
||||
|
||||
/* Helper function. */
|
||||
void ContentLocationResolverImpl::GetContentStoragePath(Path *out, ncm::ContentId content_id) {
|
||||
static_assert(sizeof(lr::Path) == sizeof(ncm::Path));
|
||||
this->content_storage.GetPath(reinterpret_cast<ncm::Path *>(out), content_id);
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
/* Use a redirection if present. */
|
||||
R_SUCCEED_IF(this->program_redirector.FindRedirection(out.GetPointer(), id));
|
||||
|
||||
/* Find the latest program content for the program id. */
|
||||
ncm::ContentId program_content_id;
|
||||
R_TRY_CATCH(this->content_meta_database.GetLatestProgram(&program_content_id, id)) {
|
||||
R_CONVERT(ncm::ResultContentMetaNotFound, lr::ResultProgramNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Obtain the content path. */
|
||||
this->GetContentStoragePath(out.GetPointer(), program_content_id);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectProgramPath(const Path &path, ncm::ProgramId id) {
|
||||
this->program_redirector.SetRedirection(id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->app_control_redirector.FindRedirection(out.GetPointer(), id), lr::ResultControlNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->html_docs_redirector.FindRedirection(out.GetPointer(), id), lr::ResultHtmlDocumentNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ResolveDataPath(sf::Out<Path> out, ncm::DataId id) {
|
||||
/* Find the latest data content for the program id. */
|
||||
ncm::ContentId data_content_id;
|
||||
R_TRY(this->content_meta_database.GetLatestData(&data_content_id, id));
|
||||
|
||||
/* Obtain the content path. */
|
||||
this->GetContentStoragePath(out.GetPointer(), data_content_id);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->app_control_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->app_control_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->html_docs_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->html_docs_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->legal_info_redirector.FindRedirection(out.GetPointer(), id), lr::ResultLegalInformationNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->legal_info_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->legal_info_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::Refresh() {
|
||||
/* Obtain content meta database and content storage objects for this resolver's storage. */
|
||||
ncm::ContentMetaDatabase meta_db;
|
||||
ncm::ContentStorage storage;
|
||||
R_TRY(ncm::OpenContentMetaDatabase(&meta_db, this->storage_id));
|
||||
R_TRY(ncm::OpenContentStorage(&storage, this->storage_id));
|
||||
|
||||
/* Store the acquired objects. */
|
||||
this->content_meta_database = std::move(meta_db);
|
||||
this->content_storage = std::move(storage);
|
||||
|
||||
/* Remove any existing redirections. */
|
||||
this->ClearRedirections();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->program_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->program_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ClearApplicationRedirectionDeprecated() {
|
||||
this->ClearRedirections(RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) {
|
||||
this->ClearRedirections(excluding_ids.GetPointer(), excluding_ids.GetSize());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::EraseProgramRedirection(ncm::ProgramId id) {
|
||||
this->program_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::EraseApplicationControlRedirection(ncm::ProgramId id) {
|
||||
this->app_control_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) {
|
||||
this->html_docs_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::EraseApplicationLegalInformationRedirection(ncm::ProgramId id) {
|
||||
this->legal_info_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
/* Use a redirection if present. */
|
||||
R_SUCCEED_IF(this->debug_program_redirector.FindRedirection(out.GetPointer(), id));
|
||||
|
||||
/* Otherwise find the path for the program id. */
|
||||
R_TRY_CATCH(this->ResolveProgramPath(out.GetPointer(), id)) {
|
||||
R_CONVERT(ResultProgramNotFound, lr::ResultDebugProgramNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) {
|
||||
this->debug_program_redirector.SetRedirection(id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->debug_program_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->debug_program_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentLocationResolverImpl::EraseProgramRedirectionForDebug(ncm::ProgramId id) {
|
||||
this->debug_program_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "lr_location_resolver_impl_base.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class ContentLocationResolverImpl : public LocationResolverImplBase {
|
||||
private:
|
||||
ncm::StorageId storage_id;
|
||||
|
||||
/* Objects for this storage type. */
|
||||
ncm::ContentMetaDatabase content_meta_database;
|
||||
ncm::ContentStorage content_storage;
|
||||
public:
|
||||
ContentLocationResolverImpl(ncm::StorageId storage_id) : storage_id(storage_id) { /* ... */ }
|
||||
|
||||
~ContentLocationResolverImpl();
|
||||
private:
|
||||
/* Helper functions. */
|
||||
void GetContentStoragePath(Path *out, ncm::ContentId content_id);
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id) override;
|
||||
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result Refresh() override;
|
||||
virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result ClearApplicationRedirectionDeprecated() override;
|
||||
virtual Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) override;
|
||||
virtual Result EraseProgramRedirection(ncm::ProgramId id) override;
|
||||
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) override;
|
||||
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) override;
|
||||
virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) override;
|
||||
virtual Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId id) override;
|
||||
};
|
||||
|
||||
}
|
127
libraries/libstratosphere/source/lr/lr_location_redirector.cpp
Normal file
127
libraries/libstratosphere/source/lr/lr_location_redirector.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_location_redirector.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class LocationRedirector::Redirection : public util::IntrusiveListBaseNode<Redirection> {
|
||||
NON_COPYABLE(Redirection);
|
||||
NON_MOVEABLE(Redirection);
|
||||
private:
|
||||
ncm::ProgramId program_id;
|
||||
ncm::ProgramId owner_id;
|
||||
Path path;
|
||||
u32 flags;
|
||||
public:
|
||||
Redirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path& path, u32 flags) :
|
||||
program_id(program_id), owner_id(owner_id), path(path), flags(flags) { /* ... */ }
|
||||
|
||||
ncm::ProgramId GetProgramId() const {
|
||||
return this->program_id;
|
||||
}
|
||||
|
||||
ncm::ProgramId GetOwnerProgramId() const {
|
||||
return this->owner_id;
|
||||
}
|
||||
|
||||
void GetPath(Path *out) const {
|
||||
*out = this->path;
|
||||
}
|
||||
|
||||
u32 GetFlags() const {
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
void SetFlags(u32 flags) {
|
||||
this->flags = flags;
|
||||
}
|
||||
};
|
||||
|
||||
bool LocationRedirector::FindRedirection(Path *out, ncm::ProgramId program_id) const {
|
||||
/* Obtain the path of a matching redirection. */
|
||||
for (const auto &redirection : this->redirection_list) {
|
||||
if (redirection.GetProgramId() == program_id) {
|
||||
redirection.GetPath(out);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocationRedirector::SetRedirection(ncm::ProgramId program_id, const Path &path, u32 flags) {
|
||||
this->SetRedirection(program_id, ncm::InvalidProgramId, path, flags);
|
||||
}
|
||||
|
||||
void LocationRedirector::SetRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, u32 flags) {
|
||||
/* Remove any existing redirections for this program id. */
|
||||
this->EraseRedirection(program_id);
|
||||
|
||||
/* Insert a new redirection into the list. */
|
||||
this->redirection_list.push_back(*(new Redirection(program_id, owner_id, path, flags)));
|
||||
}
|
||||
|
||||
void LocationRedirector::SetRedirectionFlags(ncm::ProgramId program_id, u32 flags) {
|
||||
/* Set the flags of a redirection with a matching program id. */
|
||||
for (auto &redirection : this->redirection_list) {
|
||||
if (redirection.GetProgramId() == program_id) {
|
||||
redirection.SetFlags(flags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocationRedirector::EraseRedirection(ncm::ProgramId program_id)
|
||||
{
|
||||
/* Remove any redirections with a matching program id. */
|
||||
for (auto &redirection : this->redirection_list) {
|
||||
if (redirection.GetProgramId() == program_id) {
|
||||
this->redirection_list.erase(this->redirection_list.iterator_to(redirection));
|
||||
delete &redirection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocationRedirector::ClearRedirections(u32 flags) {
|
||||
/* Remove any redirections with matching flags. */
|
||||
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end();) {
|
||||
if ((it->GetFlags() & flags) == flags) {
|
||||
auto old = it;
|
||||
it = this->redirection_list.erase(it);
|
||||
delete std::addressof(*old);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocationRedirector::ClearRedirectionsExcludingOwners(const ncm::ProgramId *excluding_ids, size_t num_ids) {
|
||||
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end();) {
|
||||
/* Skip removal if the redirection has an excluded owner program id. */
|
||||
if (this->IsExcluded(it->GetOwnerProgramId(), excluding_ids, num_ids)) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Remove the redirection. */
|
||||
auto old = it;
|
||||
it = this->redirection_list.erase(it);
|
||||
delete std::addressof(*old);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
enum RedirectionFlags {
|
||||
RedirectionFlags_None = (0 << 0),
|
||||
RedirectionFlags_Application = (1 << 0),
|
||||
};
|
||||
|
||||
class LocationRedirector {
|
||||
NON_COPYABLE(LocationRedirector);
|
||||
NON_MOVEABLE(LocationRedirector);
|
||||
private:
|
||||
class Redirection;
|
||||
private:
|
||||
using RedirectionList = ams::util::IntrusiveListBaseTraits<Redirection>::ListType;
|
||||
private:
|
||||
RedirectionList redirection_list;
|
||||
public:
|
||||
LocationRedirector() { /* ... */ }
|
||||
~LocationRedirector() { this->ClearRedirections(); }
|
||||
|
||||
/* API. */
|
||||
bool FindRedirection(Path *out, ncm::ProgramId program_id) const;
|
||||
void SetRedirection(ncm::ProgramId program_id, const Path &path, u32 flags = RedirectionFlags_None);
|
||||
void SetRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, u32 flags = RedirectionFlags_None);
|
||||
void SetRedirectionFlags(ncm::ProgramId program_id, u32 flags);
|
||||
void EraseRedirection(ncm::ProgramId program_id);
|
||||
void ClearRedirections(u32 flags = RedirectionFlags_None);
|
||||
void ClearRedirectionsExcludingOwners(const ncm::ProgramId *excluding_ids, size_t num_ids);
|
||||
private:
|
||||
inline bool IsExcluded(const ncm::ProgramId id, const ncm::ProgramId *excluding_ids, size_t num_ids) const {
|
||||
for (size_t i = 0; i < num_ids; i++) {
|
||||
if (id == excluding_ids[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_location_redirector.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class LocationResolverImplBase : public ILocationResolver {
|
||||
NON_COPYABLE(LocationResolverImplBase);
|
||||
NON_MOVEABLE(LocationResolverImplBase);
|
||||
protected:
|
||||
/* Location redirectors. */
|
||||
LocationRedirector program_redirector;
|
||||
LocationRedirector debug_program_redirector;
|
||||
LocationRedirector app_control_redirector;
|
||||
LocationRedirector html_docs_redirector;
|
||||
LocationRedirector legal_info_redirector;
|
||||
protected:
|
||||
LocationResolverImplBase() : program_redirector(), debug_program_redirector(), app_control_redirector(), html_docs_redirector(), legal_info_redirector() { /* ... */ }
|
||||
protected:
|
||||
/* Helper functions. */
|
||||
void ClearRedirections(u32 flags = RedirectionFlags_None) {
|
||||
this->program_redirector.ClearRedirections(flags);
|
||||
this->debug_program_redirector.ClearRedirections(flags);
|
||||
this->app_control_redirector.ClearRedirections(flags);
|
||||
this->html_docs_redirector.ClearRedirections(flags);
|
||||
this->legal_info_redirector.ClearRedirections(flags);
|
||||
}
|
||||
|
||||
void ClearRedirections(const ncm::ProgramId *excluding_ids, size_t num_ids) {
|
||||
this->program_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
this->debug_program_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
this->app_control_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
this->html_docs_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
this->legal_info_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/lr/lr_location_resolver_manager_impl.hpp>
|
||||
#include "lr_content_location_resolver_impl.hpp"
|
||||
#include "lr_redirect_only_location_resolver_impl.hpp"
|
||||
#include "lr_add_on_content_location_resolver_impl.hpp"
|
||||
#include "lr_registered_location_resolver_impl.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
Result LocationResolverManagerImpl::OpenLocationResolver(sf::Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
/* Find an existing resolver. */
|
||||
auto resolver = this->location_resolvers.Find(storage_id);
|
||||
|
||||
/* No existing resolver is present, create one. */
|
||||
if (!resolver) {
|
||||
if (storage_id == ncm::StorageId::Host) {
|
||||
AMS_ABORT_UNLESS(this->location_resolvers.Insert(storage_id, std::make_shared<RedirectOnlyLocationResolverImpl>()));
|
||||
} else {
|
||||
auto content_resolver = std::make_shared<ContentLocationResolverImpl>(storage_id);
|
||||
R_TRY(content_resolver->Refresh());
|
||||
AMS_ABORT_UNLESS(this->location_resolvers.Insert(storage_id, std::move(content_resolver)));
|
||||
}
|
||||
|
||||
/* Acquire the newly-created resolver. */
|
||||
resolver = this->location_resolvers.Find(storage_id);
|
||||
}
|
||||
|
||||
/* Copy the output interface. */
|
||||
out.SetValue(std::shared_ptr<ILocationResolver>(*resolver));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result LocationResolverManagerImpl::OpenRegisteredLocationResolver(sf::Out<std::shared_ptr<IRegisteredLocationResolver>> out) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* No existing resolver is present, create one. */
|
||||
if (!this->registered_location_resolver) {
|
||||
this->registered_location_resolver = std::make_shared<RegisteredLocationResolverImpl>();
|
||||
}
|
||||
|
||||
/* Copy the output interface. */
|
||||
out.SetValue(std::shared_ptr<IRegisteredLocationResolver>(this->registered_location_resolver));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result LocationResolverManagerImpl::RefreshLocationResolver(ncm::StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Attempt to find an existing resolver. */
|
||||
auto resolver = this->location_resolvers.Find(storage_id);
|
||||
R_UNLESS(resolver, lr::ResultUnknownStorageId());
|
||||
|
||||
/* Refresh the resolver. */
|
||||
if (storage_id != ncm::StorageId::Host) {
|
||||
(*resolver)->Refresh();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result LocationResolverManagerImpl::OpenAddOnContentLocationResolver(sf::Out<std::shared_ptr<IAddOnContentLocationResolver>> out) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* No existing resolver is present, create one. */
|
||||
if (!this->add_on_content_location_resolver) {
|
||||
this->add_on_content_location_resolver = std::make_shared<AddOnContentLocationResolverImpl>();
|
||||
}
|
||||
|
||||
/* Copy the output interface. */
|
||||
out.SetValue(std::shared_ptr<IAddOnContentLocationResolver>(this->add_on_content_location_resolver));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_redirect_only_location_resolver_impl.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
RedirectOnlyLocationResolverImpl::~RedirectOnlyLocationResolverImpl() {
|
||||
/* Ensure entries are deallocated */
|
||||
this->ClearRedirections();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->program_redirector.FindRedirection(out.GetPointer(), id), lr::ResultProgramNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectProgramPath(const Path &path, ncm::ProgramId id) {
|
||||
this->program_redirector.SetRedirection(id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->app_control_redirector.FindRedirection(out.GetPointer(), id), lr::ResultControlNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->html_docs_redirector.FindRedirection(out.GetPointer(), id), lr::ResultHtmlDocumentNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ResolveDataPath(sf::Out<Path> out, ncm::DataId id) {
|
||||
return ResultDataNotFound();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->app_control_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->app_control_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->html_docs_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->html_docs_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(this->legal_info_redirector.FindRedirection(out.GetPointer(), id), lr::ResultLegalInformationNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->legal_info_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->legal_info_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::Refresh() {
|
||||
this->ClearRedirections();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->program_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->program_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ClearApplicationRedirectionDeprecated() {
|
||||
this->ClearRedirections(RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) {
|
||||
this->ClearRedirections(excluding_ids.GetPointer(), excluding_ids.GetSize());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::EraseProgramRedirection(ncm::ProgramId id) {
|
||||
this->program_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::EraseApplicationControlRedirection(ncm::ProgramId id) {
|
||||
this->app_control_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) {
|
||||
this->html_docs_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::EraseApplicationLegalInformationRedirection(ncm::ProgramId id) {
|
||||
this->legal_info_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
/* If a debug program redirection is present, use it. */
|
||||
R_SUCCEED_IF(this->debug_program_redirector.FindRedirection(out.GetPointer(), id));
|
||||
|
||||
/* Otherwise, try to find a normal program redirection. */
|
||||
R_UNLESS(this->program_redirector.FindRedirection(out.GetPointer(), id), lr::ResultDebugProgramNotFound());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) {
|
||||
this->debug_program_redirector.SetRedirection(id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->debug_program_redirector.SetRedirection(id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->debug_program_redirector.SetRedirection(id, owner_id, path, RedirectionFlags_Application);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverImpl::EraseProgramRedirectionForDebug(ncm::ProgramId id) {
|
||||
this->debug_program_redirector.EraseRedirection(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "lr_location_resolver_impl_base.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class RedirectOnlyLocationResolverImpl : public LocationResolverImplBase {
|
||||
public:
|
||||
~RedirectOnlyLocationResolverImpl();
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id) override;
|
||||
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result Refresh() override;
|
||||
virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result ClearApplicationRedirectionDeprecated() override;
|
||||
virtual Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) override;
|
||||
virtual Result EraseProgramRedirection(ncm::ProgramId id) override;
|
||||
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) override;
|
||||
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) override;
|
||||
virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) override;
|
||||
virtual Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId id) override;
|
||||
};
|
||||
|
||||
}
|
145
libraries/libstratosphere/source/lr/lr_registered_data.hpp
Normal file
145
libraries/libstratosphere/source/lr/lr_registered_data.hpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere/lr/lr_types.hpp>
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
template<typename Key, typename Value, size_t NumEntries>
|
||||
class RegisteredData {
|
||||
NON_COPYABLE(RegisteredData);
|
||||
NON_MOVEABLE(RegisteredData);
|
||||
private:
|
||||
struct Entry {
|
||||
Value value;
|
||||
ncm::ProgramId owner_id;
|
||||
Key key;
|
||||
bool is_valid;
|
||||
};
|
||||
private:
|
||||
Entry entries[NumEntries];
|
||||
size_t capacity;
|
||||
private:
|
||||
inline bool IsExcluded(const ncm::ProgramId id, const ncm::ProgramId *excluding_ids, size_t num_ids) const {
|
||||
/* Try to find program id in exclusions. */
|
||||
for (size_t i = 0; i < num_ids; i++) {
|
||||
if (id == excluding_ids[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void RegisterImpl(size_t i, const Key &key, const Value &value, const ncm::ProgramId owner_id) {
|
||||
/* Populate entry. */
|
||||
Entry& entry = this->entries[i];
|
||||
entry.key = key;
|
||||
entry.value = value;
|
||||
entry.owner_id = owner_id;
|
||||
entry.is_valid = true;
|
||||
}
|
||||
public:
|
||||
RegisteredData(size_t capacity = NumEntries) : capacity(capacity) {
|
||||
this->Clear();
|
||||
}
|
||||
|
||||
bool Register(const Key &key, const Value &value, const ncm::ProgramId owner_id) {
|
||||
/* Try to find an existing value. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
Entry& entry = this->entries[i];
|
||||
if (entry.is_valid && entry.key == key) {
|
||||
this->RegisterImpl(i, key, value, owner_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* We didn't find an existing entry, so try to create a new one. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
Entry& entry = this->entries[i];
|
||||
if (!entry.is_valid) {
|
||||
this->RegisterImpl(i, key, value, owner_id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Unregister(const Key &key) {
|
||||
/* Invalidate entries with a matching key. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
Entry& entry = this->entries[i];
|
||||
if (entry.is_valid && entry.key == key) {
|
||||
entry.is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnregisterOwnerProgram(ncm::ProgramId owner_id) {
|
||||
/* Invalidate entries with a matching owner id. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
Entry& entry = this->entries[i];
|
||||
if (entry.owner_id == owner_id) {
|
||||
entry.is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Find(Value *out, const Key &key) const {
|
||||
/* Locate a matching entry. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
const Entry& entry = this->entries[i];
|
||||
if (entry.is_valid && entry.key == key) {
|
||||
*out = entry.value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
/* Invalidate all entries. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
this->entries[i].is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearExcluding(const ncm::ProgramId *ids, size_t num_ids) {
|
||||
/* Invalidate all entries unless excluded. */
|
||||
for (size_t i = 0; i < this->GetCapacity(); i++) {
|
||||
Entry& entry = this->entries[i];
|
||||
|
||||
if (!this->IsExcluded(entry.owner_id, ids, num_ids)) {
|
||||
entry.is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetCapacity() const {
|
||||
return this->capacity;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Key, size_t NumEntries>
|
||||
using RegisteredLocations = RegisteredData<Key, lr::Path, NumEntries>;
|
||||
|
||||
template<typename Key, size_t NumEntries>
|
||||
using RegisteredStorages = RegisteredData<Key, ncm::StorageId, NumEntries>;
|
||||
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_registered_location_resolver_impl.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
namespace {
|
||||
|
||||
template<size_t N>
|
||||
bool ResolvePath(Path *out, const LocationRedirector &redirector, const RegisteredLocations<ncm::ProgramId, N> &locations, ncm::ProgramId id) {
|
||||
/* Attempt to use a redirection if present. */
|
||||
if (!redirector.FindRedirection(out, id)) {
|
||||
/* Otherwise try and use a registered location. */
|
||||
if (!locations.Find(out, id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
void RegisterPath(RegisteredLocations<ncm::ProgramId, N> &locations, ncm::ProgramId id, const Path& path, ncm::ProgramId owner_id) {
|
||||
/* If we register successfully, we're good. */
|
||||
if (locations.Register(id, path, owner_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise, clear and register (this should always succeed). */
|
||||
locations.Clear();
|
||||
locations.Register(id, path, owner_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RegisteredLocationResolverImpl::~RegisteredLocationResolverImpl() {
|
||||
/* Ensure entries are deallocated */
|
||||
this->ClearRedirections();
|
||||
}
|
||||
|
||||
/* Helper function. */
|
||||
void RegisteredLocationResolverImpl::ClearRedirections(u32 flags) {
|
||||
this->html_docs_redirector.ClearRedirections(flags);
|
||||
this->program_redirector.ClearRedirections(flags);
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RefreshImpl(const ncm::ProgramId *excluding_ids, size_t num_ids) {
|
||||
/* On < 9.0.0, exclusion lists were not supported yet, so simply clear and return. */
|
||||
if (hos::GetVersion() < hos::Version_900) {
|
||||
this->ClearRedirections();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
if (num_ids) {
|
||||
/* If we have exclusion lists, explicitly clear our locations. */
|
||||
this->registered_program_locations.ClearExcluding(excluding_ids, num_ids);
|
||||
this->registered_html_docs_locations.ClearExcluding(excluding_ids, num_ids);
|
||||
} else {
|
||||
/* If we don't, just perform a general clear (as pre 9.0.0 did). */
|
||||
this->ClearRedirections();
|
||||
}
|
||||
|
||||
/* Clear redirectors using exclusion lists. */
|
||||
this->program_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
this->html_docs_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(ResolvePath(out.GetPointer(), this->program_redirector, this->registered_program_locations, id), lr::ResultProgramNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
RegisterPath(this->registered_program_locations, id, path, ncm::InvalidProgramId);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
RegisterPath(this->registered_program_locations, id, path, owner_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::UnregisterProgramPath(ncm::ProgramId id) {
|
||||
this->registered_program_locations.Unregister(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->program_redirector.SetRedirection(id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->program_redirector.SetRedirection(id, owner_id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) {
|
||||
R_UNLESS(ResolvePath(out.GetPointer(), this->html_docs_redirector, this->registered_html_docs_locations, id), lr::ResultHtmlDocumentNotFound());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
RegisterPath(this->registered_html_docs_locations, id, path, ncm::InvalidProgramId);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
RegisterPath(this->registered_html_docs_locations, id, path, owner_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::UnregisterHtmlDocumentPath(ncm::ProgramId id) {
|
||||
this->registered_html_docs_locations.Unregister(id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
|
||||
this->html_docs_redirector.SetRedirection(id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
|
||||
this->html_docs_redirector.SetRedirection(id, owner_id, path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::Refresh() {
|
||||
return this->RefreshImpl(nullptr, 0);
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverImpl::RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids) {
|
||||
return this->RefreshImpl(ids.GetPointer(), ids.GetSize());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "lr_location_redirector.hpp"
|
||||
#include "lr_registered_data.hpp"
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class RegisteredLocationResolverImpl : public IRegisteredLocationResolver {
|
||||
private:
|
||||
static constexpr size_t MaxRegisteredLocationsDeprecated = 0x10;
|
||||
static constexpr size_t MaxRegisteredLocations = 0x20;
|
||||
static_assert(MaxRegisteredLocations >= MaxRegisteredLocationsDeprecated);
|
||||
private:
|
||||
static ALWAYS_INLINE size_t GetMaxRegisteredLocations() {
|
||||
if (hos::GetVersion() >= hos::Version_900) {
|
||||
return MaxRegisteredLocations;
|
||||
} else {
|
||||
return MaxRegisteredLocationsDeprecated;
|
||||
}
|
||||
}
|
||||
private:
|
||||
/* Redirection and registered location storage. */
|
||||
LocationRedirector program_redirector;
|
||||
RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations> registered_program_locations;
|
||||
LocationRedirector html_docs_redirector;
|
||||
RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations> registered_html_docs_locations;
|
||||
private:
|
||||
/* Helper functions. */
|
||||
void ClearRedirections(u32 flags = RedirectionFlags_None);
|
||||
Result RefreshImpl(const ncm::ProgramId *excluding_ids, size_t num_ids);
|
||||
public:
|
||||
RegisteredLocationResolverImpl() : registered_program_locations(GetMaxRegisteredLocations()), registered_html_docs_locations(GetMaxRegisteredLocations()) { /* ... */ }
|
||||
~RegisteredLocationResolverImpl();
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result UnregisterProgramPath(ncm::ProgramId id) override;
|
||||
virtual Result RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override;
|
||||
virtual Result RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result UnregisterHtmlDocumentPath(ncm::ProgramId id) override;
|
||||
virtual Result RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override;
|
||||
virtual Result RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
|
||||
virtual Result Refresh() override;
|
||||
virtual Result RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class RemoteLocationResolverImpl : public ILocationResolver {
|
||||
private:
|
||||
::LrLocationResolver srv;
|
||||
public:
|
||||
RemoteLocationResolverImpl(::LrLocationResolver &l) : srv(l) { /* ... */ }
|
||||
|
||||
~RemoteLocationResolverImpl() { ::serviceClose(&srv.s); }
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
return lrLrResolveProgramPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
|
||||
}
|
||||
|
||||
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) override {
|
||||
return lrLrRedirectProgramPath(std::addressof(this->srv), static_cast<u64>(id), path.str);
|
||||
}
|
||||
|
||||
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
return lrLrResolveApplicationControlPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
|
||||
}
|
||||
|
||||
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
return lrLrResolveApplicationHtmlDocumentPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
|
||||
}
|
||||
|
||||
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id) override {
|
||||
return lrLrResolveDataPath(std::addressof(this->srv), id.value, out->str);
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
return lrLrRedirectApplicationControlPath(std::addressof(this->srv), static_cast<u64>(id), 0, path.str);
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
return lrLrRedirectApplicationControlPath(std::addressof(this->srv), static_cast<u64>(id), static_cast<u64>(owner_id), path.str);
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
return lrLrRedirectApplicationHtmlDocumentPath(std::addressof(this->srv), static_cast<u64>(id), 0, path.str);
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
return lrLrRedirectApplicationHtmlDocumentPath(std::addressof(this->srv), static_cast<u64>(id), static_cast<u64>(owner_id), path.str);
|
||||
}
|
||||
|
||||
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
return lrLrResolveApplicationLegalInformationPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
return lrLrRedirectApplicationLegalInformationPath(std::addressof(this->srv), static_cast<u64>(id), 0, path.str);
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
return lrLrRedirectApplicationLegalInformationPath(std::addressof(this->srv), static_cast<u64>(id), static_cast<u64>(owner_id), path.str);
|
||||
}
|
||||
|
||||
virtual Result Refresh() override {
|
||||
return lrLrRefresh(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result ClearApplicationRedirectionDeprecated() override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result EraseProgramRedirection(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lr {
|
||||
|
||||
class RemoteRegisteredLocationResolverImpl : public IRegisteredLocationResolver {
|
||||
private:
|
||||
::LrRegisteredLocationResolver srv;
|
||||
public:
|
||||
RemoteRegisteredLocationResolverImpl(::LrRegisteredLocationResolver &l) : srv(l) { /* ... */ }
|
||||
|
||||
~RemoteRegisteredLocationResolverImpl() { ::serviceClose(&srv.s); }
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
return lrRegLrResolveProgramPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
|
||||
}
|
||||
|
||||
virtual Result RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result UnregisterProgramPath(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result UnregisterHtmlDocumentPath(ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result Refresh() override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
|
||||
virtual Result RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids) override {
|
||||
/* TODO: libnx bindings */
|
||||
AMS_ABORT();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
112
libraries/libstratosphere/source/ncm/ncm_api.cpp
Normal file
112
libraries/libstratosphere/source/ncm/ncm_api.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_remote_content_manager_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<IContentManager> g_content_manager;
|
||||
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
AMS_ASSERT(g_content_manager == nullptr);
|
||||
R_ABORT_UNLESS(ncmInitialize());
|
||||
g_content_manager = std::make_shared<RemoteContentManagerImpl>();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
AMS_ASSERT(g_content_manager != nullptr);
|
||||
g_content_manager.reset();
|
||||
ncmExit();
|
||||
}
|
||||
|
||||
void InitializeWithObject(std::shared_ptr<IContentManager> manager_object) {
|
||||
AMS_ASSERT(g_content_manager == nullptr);
|
||||
g_content_manager = manager_object;
|
||||
AMS_ASSERT(g_content_manager != nullptr);
|
||||
}
|
||||
|
||||
/* Service API. */
|
||||
Result CreateContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->CreateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result CreateContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->CreateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result VerifyContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->VerifyContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result VerifyContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->VerifyContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result OpenContentStorage(ContentStorage *out, StorageId storage_id) {
|
||||
sf::cmif::ServiceObjectHolder object_holder;
|
||||
R_TRY(g_content_manager->OpenContentStorage(std::addressof(object_holder), storage_id));
|
||||
|
||||
*out = ContentStorage(object_holder.GetServiceObject<IContentStorage>());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OpenContentMetaDatabase(ContentMetaDatabase *out, StorageId storage_id) {
|
||||
sf::cmif::ServiceObjectHolder object_holder;
|
||||
R_TRY(g_content_manager->OpenContentMetaDatabase(std::addressof(object_holder), storage_id));
|
||||
|
||||
*out = ContentMetaDatabase(object_holder.GetServiceObject<IContentMetaDatabase>());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result CleanupContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->CleanupContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result ActivateContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->ActivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result InactivateContentStorage(StorageId storage_id) {
|
||||
return g_content_manager->InactivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result ActivateContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->ActivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result InactivateContentMetaDatabase(StorageId storage_id) {
|
||||
return g_content_manager->InactivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result InvalidateRightsIdCache() {
|
||||
return g_content_manager->InvalidateRightsIdCache();
|
||||
}
|
||||
|
||||
/* Deprecated API. */
|
||||
Result CloseContentStorageForcibly(StorageId storage_id) {
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() == hos::Version_100);
|
||||
return g_content_manager->CloseContentStorageForcibly(storage_id);
|
||||
}
|
||||
|
||||
Result CloseContentMetaDatabaseForcibly(StorageId storage_id) {
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() == hos::Version_100);
|
||||
return g_content_manager->CloseContentMetaDatabaseForcibly(storage_id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void GetStringFromBytes(char *dst, const void *src, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
std::snprintf(dst + 2 * i, 3, "%02x", static_cast<const u8 *>(src)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetBytesFromString(void *dst, size_t dst_size, const char *src, size_t src_size) {
|
||||
/* Each byte is comprised of hex characters. */
|
||||
if (!util::IsAligned(src_size, 2) || (dst_size * 2 < src_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Convert each character pair to a byte until we reach the end. */
|
||||
for (size_t i = 0; i < src_size; i += 2) {
|
||||
char tmp[3];
|
||||
strlcpy(tmp, src + i, sizeof(tmp));
|
||||
|
||||
char *err = nullptr;
|
||||
reinterpret_cast<u8 *>(dst)[i / 2] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16));
|
||||
if (*err != '\x00') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
ContentIdString GetContentIdString(ContentId id) {
|
||||
ContentIdString str;
|
||||
GetStringFromContentId(str.data, sizeof(str), id);
|
||||
return str;
|
||||
}
|
||||
|
||||
void GetStringFromContentId(char *dst, size_t dst_size, ContentId id) {
|
||||
AMS_ABORT_UNLESS(dst_size > ContentIdStringLength);
|
||||
GetStringFromBytes(dst, std::addressof(id), sizeof(id));
|
||||
}
|
||||
|
||||
std::optional<ContentId> GetContentIdFromString(const char *str, size_t len) {
|
||||
if (len < ContentIdStringLength) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ContentId content_id;
|
||||
return GetBytesFromString(std::addressof(content_id), sizeof(content_id), str, ContentIdStringLength) ? std::optional<ContentId>(content_id) : std::nullopt;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline size_t MaxPackagePathLength = 0x100;
|
||||
|
||||
Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *package_root_path, const char *entry_path) {
|
||||
char package_path[MaxPackagePathLength];
|
||||
|
||||
const size_t path_len = std::snprintf(package_path, sizeof(package_path), "%s%s", package_root_path, entry_path);
|
||||
AMS_ABORT_UNLESS(path_len < MaxPackagePathLength);
|
||||
|
||||
return fs::ConvertToFsCommonPath(dst, dst_size, package_path);
|
||||
}
|
||||
|
||||
Result LoadContentMeta(ncm::AutoBuffer *out, const char *package_root_path, const fs::DirectoryEntry &entry) {
|
||||
AMS_ABORT_UNLESS(impl::PathView(entry.name).HasSuffix(".cnmt.nca"));
|
||||
|
||||
char path[MaxPackagePathLength];
|
||||
R_TRY(ConvertToFsCommonPath(path, sizeof(path), package_root_path, entry.name));
|
||||
|
||||
return ncm::ReadContentMetaPath(out, path);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result ForEachFileInDirectory(const char *root_path, F f) {
|
||||
/* Open the directory. */
|
||||
fs::DirectoryHandle dir;
|
||||
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_File));
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||
|
||||
while (true) {
|
||||
/* Read the current entry. */
|
||||
s64 count;
|
||||
fs::DirectoryEntry entry;
|
||||
R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1));
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Invoke our handler on the entry. */
|
||||
bool done;
|
||||
R_TRY(f(std::addressof(done), entry));
|
||||
R_SUCCEED_IF(done);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::BuildFromPackageContentMeta(void *buf, size_t size, const ContentInfo &meta_info) {
|
||||
/* Create a reader for the content meta. */
|
||||
ncm::PackagedContentMetaReader package_meta_reader(buf, size);
|
||||
|
||||
/* Allocate space to hold the converted meta. */
|
||||
const size_t meta_size = package_meta_reader.CalculateConvertContentMetaSize();
|
||||
void *meta = std::malloc(meta_size);
|
||||
ON_SCOPE_EXIT { std::free(meta); };
|
||||
|
||||
/* Convert the meta from packaged form to normal form. */
|
||||
package_meta_reader.ConvertToContentMeta(meta, meta_size, meta_info);
|
||||
ncm::ContentMetaReader meta_reader(meta, meta_size);
|
||||
|
||||
/* Insert the new metas into the database. */
|
||||
R_TRY(this->db->Set(package_meta_reader.GetKey(), meta_reader.GetData(), meta_reader.GetSize()));
|
||||
|
||||
/* We're done. */
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::BuildFromStorage(ContentStorage *storage) {
|
||||
/* Get the total count of contents. */
|
||||
s32 total_count;
|
||||
R_TRY(storage->GetContentCount(std::addressof(total_count)));
|
||||
|
||||
/* Loop over all contents, looking for a package we can build from. */
|
||||
const size_t MaxContentIds = 64;
|
||||
ContentId content_ids[MaxContentIds];
|
||||
for (s32 offset = 0; offset < total_count; /* ... */) {
|
||||
/* List contents at the current offset. */
|
||||
s32 count;
|
||||
R_TRY(storage->ListContentId(std::addressof(count), content_ids, MaxContentIds, offset));
|
||||
|
||||
/* Loop the contents we listed, looking for a correct one. */
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
/* Get the path for this content id. */
|
||||
auto &content_id = content_ids[i];
|
||||
ncm::Path path;
|
||||
storage->GetPath(std::addressof(path), content_id);
|
||||
|
||||
/* Read the content meta path, and build. */
|
||||
ncm::AutoBuffer package_meta;
|
||||
if (R_SUCCEEDED(ncm::ReadContentMetaPath(std::addressof(package_meta), path.str))) {
|
||||
/* Get the size of the content. */
|
||||
s64 size;
|
||||
R_TRY(storage->GetSize(std::addressof(size), content_id));
|
||||
|
||||
/* Build. */
|
||||
R_TRY(this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(content_id, size, ContentType::Meta)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
offset += count;
|
||||
}
|
||||
|
||||
/* Commit our changes. */
|
||||
return this->db->Commit();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::BuildFromPackage(const char *package_root_path) {
|
||||
/* Build the database by writing every entry in the package. */
|
||||
R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result {
|
||||
/* Never early terminate. */
|
||||
*done = false;
|
||||
|
||||
/* We have nothing to list if we're not looking at a meta. */
|
||||
R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca"));
|
||||
|
||||
/* Read the content meta path, and build. */
|
||||
ncm::AutoBuffer package_meta;
|
||||
R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry));
|
||||
|
||||
/* Try to parse a content id from the name. */
|
||||
auto content_id = GetContentIdFromString(entry.name, sizeof(entry.name));
|
||||
R_UNLESS(content_id, ncm::ResultInvalidPackageFormat());
|
||||
|
||||
/* Build using the meta. */
|
||||
return this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(*content_id, entry.file_size, ContentType::Meta));
|
||||
}));
|
||||
|
||||
/* Commit our changes. */
|
||||
return this->db->Commit();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseBuilder::Cleanup() {
|
||||
/* This cleans up the content meta by removing all entries. */
|
||||
while (true) {
|
||||
/* List as many keys as we can. */
|
||||
constexpr s32 MaxKeys = 64;
|
||||
ContentMetaKey keys[MaxKeys];
|
||||
auto list_count = this->db->ListContentMeta(keys, MaxKeys);
|
||||
|
||||
/* Remove the listed keys. */
|
||||
for (auto i = 0; i < list_count.written; i++) {
|
||||
R_TRY(this->db->Remove(keys[i]));
|
||||
}
|
||||
|
||||
/* If there aren't more keys to read, we're done. */
|
||||
if (list_count.written < MaxKeys) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Commit our deletions. */
|
||||
return this->db->Commit();
|
||||
}
|
||||
|
||||
Result ListApplicationPackage(s32 *out_count, ApplicationId *out_ids, size_t max_out_ids, const char *package_root_path) {
|
||||
size_t count = 0;
|
||||
R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result {
|
||||
/* Never early terminate. */
|
||||
*done = false;
|
||||
|
||||
/* We have nothing to list if we're not looking at a meta. */
|
||||
R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca"));
|
||||
|
||||
/* Read the content meta path, and build. */
|
||||
ncm::AutoBuffer package_meta;
|
||||
R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry));
|
||||
|
||||
/* Create a reader for the meta. */
|
||||
ncm::PackagedContentMetaReader package_meta_reader(package_meta.Get(), package_meta.GetSize());
|
||||
|
||||
/* Write the key to output if we're reading an application. */
|
||||
const auto &key = package_meta_reader.GetKey();
|
||||
if (key.type == ContentMetaType::Application) {
|
||||
R_UNLESS(count < max_out_ids, ncm::ResultBufferInsufficient());
|
||||
|
||||
out_ids[count++] = { key.id };
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
*out_count = static_cast<s32>(count);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,663 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_storage_impl.hpp"
|
||||
#include "ncm_read_only_content_storage_impl.hpp"
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
#include "ncm_on_memory_content_meta_database_impl.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr fs::SystemSaveDataId BuiltInSystemSaveDataId = 0x8000000000000120;
|
||||
constexpr u64 BuiltInSystemSaveDataSize = 0x6c000;
|
||||
constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000;
|
||||
constexpr u32 BuiltInSystemSaveDataFlags = fs::SaveDataFlags_KeepAfterResettingSystemSaveData | fs::SaveDataFlags_KeepAfterRefurbishment;
|
||||
|
||||
constexpr SystemSaveDataInfo BuiltInSystemSystemSaveDataInfo = {
|
||||
.id = BuiltInSystemSaveDataId,
|
||||
.size = BuiltInSystemSaveDataSize,
|
||||
.journal_size = BuiltInSystemSaveDataJournalSize,
|
||||
.flags = BuiltInSystemSaveDataFlags,
|
||||
.space_id = fs::SaveDataSpaceId::System
|
||||
};
|
||||
|
||||
constexpr fs::SystemSaveDataId BuiltInUserSaveDataId = 0x8000000000000121;
|
||||
constexpr u64 BuiltInUserSaveDataSize = 0x29e000;
|
||||
constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000;
|
||||
constexpr u32 BuiltInUserSaveDataFlags = 0;
|
||||
|
||||
constexpr SystemSaveDataInfo BuiltInUserSystemSaveDataInfo = {
|
||||
.id = BuiltInUserSaveDataId,
|
||||
.size = BuiltInUserSaveDataSize,
|
||||
.journal_size = BuiltInUserSaveDataJournalSize,
|
||||
.flags = BuiltInUserSaveDataFlags,
|
||||
.space_id = fs::SaveDataSpaceId::System
|
||||
};
|
||||
|
||||
constexpr fs::SystemSaveDataId SdCardSaveDataId = 0x8000000000000124;
|
||||
constexpr u64 SdCardSaveDataSize = 0xa08000;
|
||||
constexpr u64 SdCardSaveDataJournalSize = 0xa08000;
|
||||
constexpr u32 SdCardSaveDataFlags = 0;
|
||||
|
||||
constexpr SystemSaveDataInfo SdCardSystemSaveDataInfo = {
|
||||
.id = SdCardSaveDataId,
|
||||
.size = SdCardSaveDataSize,
|
||||
.journal_size = SdCardSaveDataJournalSize,
|
||||
.flags = SdCardSaveDataFlags,
|
||||
.space_id = fs::SaveDataSpaceId::SdSystem,
|
||||
};
|
||||
|
||||
constexpr size_t MaxBuiltInSystemContentMetaCount = 0x800;
|
||||
constexpr size_t MaxBuiltInUserContentMetaCount = 0x2000;
|
||||
constexpr size_t MaxSdCardContentMetaCount = 0x2000;
|
||||
constexpr size_t MaxGameCardContentMetaCount = 0x800;
|
||||
|
||||
using RootPath = kvdb::BoundedString<32>;
|
||||
|
||||
inline void ReplaceMountName(char *out_path, const char *mount_name, const char *path) {
|
||||
std::strcpy(out_path, mount_name);
|
||||
std::strcat(out_path, std::strchr(path, ':'));
|
||||
}
|
||||
|
||||
Result EnsureBuiltInSystemSaveDataFlags() {
|
||||
u32 cur_flags = 0;
|
||||
|
||||
/* Obtain the existing flags. */
|
||||
R_TRY(fs::GetSaveDataFlags(std::addressof(cur_flags), BuiltInSystemSaveDataId));
|
||||
|
||||
/* Update the flags if needed. */
|
||||
if (cur_flags != BuiltInSystemSaveDataFlags) {
|
||||
R_TRY(fs::SetSaveDataFlags(BuiltInSystemSaveDataId, fs::SaveDataSpaceId::System, BuiltInSystemSaveDataFlags));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) {
|
||||
switch (storage_id) {
|
||||
case StorageId::GameCard: return ResultGameCardContentStorageNotActive();
|
||||
case StorageId::BuiltInSystem: return ResultBuiltInSystemContentStorageNotActive();
|
||||
case StorageId::BuiltInUser: return ResultBuiltInUserContentStorageNotActive();
|
||||
case StorageId::SdCard: return ResultSdCardContentStorageNotActive();
|
||||
default: return ResultUnknownContentStorageNotActive();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) {
|
||||
switch (storage_id) {
|
||||
case StorageId::GameCard: return ResultGameCardContentMetaDatabaseNotActive();
|
||||
case StorageId::BuiltInSystem: return ResultBuiltInSystemContentMetaDatabaseNotActive();
|
||||
case StorageId::BuiltInUser: return ResultBuiltInUserContentMetaDatabaseNotActive();
|
||||
case StorageId::SdCard: return ResultSdCardContentMetaDatabaseNotActive();
|
||||
default: return ResultUnknownContentMetaDatabaseNotActive();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool IsSignedSystemPartitionOnSdCardValid(const char *bis_mount_name) {
|
||||
/* Signed system partition should never be checked on < 4.0.0, as it did not exist before then. */
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() >= hos::Version_400);
|
||||
|
||||
/* If we're importing from system on SD, make sure that the signed system partition is valid. */
|
||||
const auto version = hos::GetVersion();
|
||||
if (version >= hos::Version_800) {
|
||||
/* On >= 8.0.0, a simpler method was added to check validity. */
|
||||
/* This also works on < 4.0.0 (though the system partition will never be on-sd there), */
|
||||
/* and so this will always return false. */
|
||||
char path[fs::MountNameLengthMax + 2 /* :/ */ + 1];
|
||||
std::snprintf(path, sizeof(path), "%s:/", bis_mount_name);
|
||||
return fs::IsSignedSystemPartitionOnSdCardValid(path);
|
||||
} else {
|
||||
/* On 4.0.0-7.0.1, use the remote command to validate the system partition. */
|
||||
return fs::IsSignedSystemPartitionOnSdCardValidDeprecated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentManagerImpl::~ContentManagerImpl() {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Disable and unmount all content storage roots. */
|
||||
for (auto &root : this->content_storage_roots) {
|
||||
this->InactivateContentStorage(root.storage_id);
|
||||
}
|
||||
|
||||
/* Disable and unmount all content meta database roots. */
|
||||
for (auto &root : this->content_meta_database_roots) {
|
||||
this->InactivateContentMetaDatabase(root.storage_id);
|
||||
}
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) const {
|
||||
constexpr u64 OwnerId = 0;
|
||||
|
||||
/* Don't create save if absent - We want to handle this case ourselves. */
|
||||
fs::DisableAutoSaveDataCreation();
|
||||
|
||||
/* Mount existing system save data if present, otherwise create it then mount. */
|
||||
R_TRY_CATCH(fs::MountSystemSaveData(mount_name, info.space_id, info.id)) {
|
||||
R_CATCH(fs::ResultTargetNotFound) {
|
||||
/* On 1.0.0, not all flags existed. Mask when appropriate. */
|
||||
constexpr u32 SaveDataFlags100Mask = fs::SaveDataFlags_KeepAfterResettingSystemSaveData;
|
||||
const u32 flags = (hos::GetVersion() >= hos::Version_200) ? (info.flags) : (info.flags & SaveDataFlags100Mask);
|
||||
R_TRY(fs::CreateSystemSaveData(info.space_id, info.id, OwnerId, info.size, info.journal_size, flags));
|
||||
R_TRY(fs::MountSystemSaveData(mount_name, info.space_id, info.id));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::GetContentStorageRoot(ContentStorageRoot **out, StorageId id) {
|
||||
/* Storage must not be StorageId::Any or StorageId::None. */
|
||||
R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage());
|
||||
|
||||
/* Find a root with a matching storage id. */
|
||||
for (auto &root : this->content_storage_roots) {
|
||||
if (root.storage_id == id) {
|
||||
*out = std::addressof(root);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return ncm::ResultUnknownStorage();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id) {
|
||||
/* Storage must not be StorageId::Any or StorageId::None. */
|
||||
R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage());
|
||||
|
||||
/* Find a root with a matching storage id. */
|
||||
for (auto &root : this->content_meta_database_roots) {
|
||||
if (root.storage_id == id) {
|
||||
*out = std::addressof(root);
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return ncm::ResultUnknownStorage();
|
||||
}
|
||||
|
||||
|
||||
Result ContentManagerImpl::InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, fs::ContentStorageId content_storage_id) {
|
||||
out->storage_id = storage_id;
|
||||
out->content_storage_id = content_storage_id;
|
||||
out->content_storage = nullptr;
|
||||
|
||||
/* Create a new mount name and copy it to out. */
|
||||
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
||||
std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InitializeGameCardContentStorageRoot(ContentStorageRoot *out) {
|
||||
out->storage_id = StorageId::GameCard;
|
||||
out->content_storage = nullptr;
|
||||
|
||||
/* Create a new mount name and copy it to out. */
|
||||
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
||||
std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, const SystemSaveDataInfo &info, size_t max_content_metas) {
|
||||
out->storage_id = storage_id;
|
||||
out->info = info;
|
||||
out->max_content_metas = max_content_metas;
|
||||
out->content_meta_database = nullptr;
|
||||
out->kvs = std::nullopt;
|
||||
|
||||
/* Create a new mount name and copy it to out. */
|
||||
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
|
||||
out->mount_name[0] = '#';
|
||||
std::snprintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InitializeGameCardContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, size_t max_content_metas) {
|
||||
out->storage_id = StorageId::GameCard;
|
||||
out->max_content_metas = max_content_metas;
|
||||
out->content_meta_database = nullptr;
|
||||
out->kvs = std::nullopt;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::ImportContentMetaDatabaseImpl(StorageId storage_id, const char *import_mount_name, const char *path) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Print the savedata path. */
|
||||
PathString savedata_db_path;
|
||||
savedata_db_path.SetFormat("%s/%s", root->path, "imkvdb.arc");
|
||||
|
||||
/* Print a path for the mounted partition. */
|
||||
PathString bis_db_path;
|
||||
bis_db_path.SetFormat("%s:/%s", import_mount_name, path);
|
||||
|
||||
/* Mount the savedata. */
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Ensure the path exists for us to import to. */
|
||||
R_TRY(impl::EnsureDirectoryRecursively(root->path));
|
||||
|
||||
/* Copy the file from bis to our save. */
|
||||
R_TRY(impl::CopyFile(savedata_db_path, bis_db_path));
|
||||
|
||||
/* Commit the import. */
|
||||
return fs::CommitSaveData(root->mount_name);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::BuildContentMetaDatabase(StorageId storage_id) {
|
||||
if (hos::GetVersion() <= hos::Version_400) {
|
||||
/* Temporarily activate the database. */
|
||||
R_TRY(this->ActivateContentMetaDatabase(storage_id));
|
||||
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); };
|
||||
|
||||
/* Open the content meta database and storage. */
|
||||
ContentMetaDatabase meta_db;
|
||||
ContentStorage storage;
|
||||
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id));
|
||||
R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id));
|
||||
|
||||
/* Create a builder, and build. */
|
||||
ContentMetaDatabaseBuilder builder(std::addressof(meta_db));
|
||||
return builder.BuildFromStorage(std::addressof(storage));
|
||||
} else {
|
||||
/* On 5.0.0+, building just performs an import. */
|
||||
return this->ImportContentMetaDatabase(storage_id, false);
|
||||
}
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition) {
|
||||
/* Only support importing BuiltInSystem. */
|
||||
AMS_ABORT_UNLESS(storage_id == StorageId::BuiltInSystem);
|
||||
|
||||
/* Get a mount name for the system partition. */
|
||||
auto bis_mount_name = impl::CreateUniqueMountName();
|
||||
|
||||
/* Mount the BIS partition that contains the database we're importing. */
|
||||
R_TRY(fs::MountBis(bis_mount_name.str, fs::BisPartitionId::System));
|
||||
ON_SCOPE_EXIT { fs::Unmount(bis_mount_name.str); };
|
||||
|
||||
/* If we're not importing from a signed partition (or the partition signature is valid), import. */
|
||||
if (!from_signed_partition || IsSignedSystemPartitionOnSdCardValid(bis_mount_name.str)) {
|
||||
R_TRY(this->ImportContentMetaDatabaseImpl(StorageId::BuiltInSystem, bis_mount_name.str, "cnmtdb.arc"));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Check if we've already initialized. */
|
||||
R_SUCCEED_IF(this->initialized);
|
||||
|
||||
/* Clear storage id for all roots. */
|
||||
for (auto &root : this->content_storage_roots) {
|
||||
root.storage_id = StorageId::None;
|
||||
}
|
||||
|
||||
for (auto &root : this->content_meta_database_roots) {
|
||||
root.storage_id = StorageId::None;
|
||||
}
|
||||
|
||||
/* First, setup the BuiltInSystem storage entry. */
|
||||
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInSystem, fs::ContentStorageId::System));
|
||||
if (R_FAILED(this->VerifyContentStorage(StorageId::BuiltInSystem))) {
|
||||
R_TRY(this->CreateContentStorage(StorageId::BuiltInSystem));
|
||||
}
|
||||
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
|
||||
|
||||
/* Next, the BuiltInSystem content meta entry. */
|
||||
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount));
|
||||
|
||||
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
|
||||
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
|
||||
/* Try to build or import a database, depending on our configuration. */
|
||||
if (config.ShouldBuildDatabase()) {
|
||||
/* If we should build the database, do so. */
|
||||
R_TRY(this->BuildContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
} else if (config.ShouldImportDatabaseFromSignedSystemPartitionOnSd()) {
|
||||
/* Otherwise if we should import the database from the SD, do so. */
|
||||
R_TRY(this->ImportContentMetaDatabase(StorageId::BuiltInSystem, true));
|
||||
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure correct flags on the BuiltInSystem save data. */
|
||||
/* NOTE: Nintendo does not check this succeeds, and it does on older system versions. */
|
||||
/* We will not check the error, either, even though this kind of defeats the call's purpose. */
|
||||
if (hos::GetVersion() >= hos::Version_200) {
|
||||
EnsureBuiltInSystemSaveDataFlags();
|
||||
}
|
||||
|
||||
R_TRY(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem));
|
||||
|
||||
/* Now for BuiltInUser's content storage and content meta entries. */
|
||||
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInUser, fs::ContentStorageId::User));
|
||||
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount));
|
||||
|
||||
/* Beyond this point, N uses hardcoded indices. */
|
||||
|
||||
/* Next SdCard's content storage and content meta entries. */
|
||||
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[2], StorageId::SdCard, fs::ContentStorageId::SdCard));
|
||||
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[2], StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount));
|
||||
|
||||
/* GameCard's content storage and content meta entries. */
|
||||
/* N doesn't set a content storage id for game cards, so we'll just use 0 (System). */
|
||||
R_TRY(this->InitializeGameCardContentStorageRoot(&this->content_storage_roots[3]));
|
||||
R_TRY(this->InitializeGameCardContentMetaDatabaseRoot(&this->content_meta_database_roots[3], MaxGameCardContentMetaCount));
|
||||
|
||||
this->initialized = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CreateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Mount the relevant content storage. */
|
||||
R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id));
|
||||
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Ensure the content storage root's path exists. */
|
||||
R_TRY(impl::EnsureDirectoryRecursively(root->path));
|
||||
|
||||
/* Initialize content and placeholder directories for the root. */
|
||||
return ContentStorageImpl::InitializeBase(root->path);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CreateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
R_UNLESS(storage_id != StorageId::GameCard, ncm::ResultUnknownStorage());
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Mount (and optionally create) save data for the root. */
|
||||
R_TRY(this->EnsureAndMountSystemSaveData(root->mount_name, root->info));
|
||||
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Ensure the content meta database root's path exists. */
|
||||
R_TRY(impl::EnsureDirectoryRecursively(root->path));
|
||||
|
||||
/* Commit our changes. */
|
||||
return fs::CommitSaveData(root->mount_name);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::VerifyContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Substitute the mount name in the root's path with a unique one. */
|
||||
char path[0x80];
|
||||
auto mount_name = impl::CreateUniqueMountName();
|
||||
ReplaceMountName(path, mount_name.str, root->path);
|
||||
|
||||
/* Mount the relevant content storage. */
|
||||
R_TRY(fs::MountContentStorage(mount_name.str, root->content_storage_id));
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name.str); };
|
||||
|
||||
/* Ensure the root, content and placeholder directories exist for the storage. */
|
||||
return ContentStorageImpl::VerifyBase(path);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) {
|
||||
/* Game card content meta databases will always be valid. */
|
||||
R_SUCCEED_IF(storage_id == StorageId::GameCard);
|
||||
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Mount save data for non-existing content meta databases. */
|
||||
const bool mount = !root->content_meta_database;
|
||||
if (mount) {
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
}
|
||||
auto mount_guard = SCOPE_GUARD { if (mount) { fs::Unmount(root->mount_name); } };
|
||||
|
||||
/* Ensure the root path exists. */
|
||||
bool has_dir = false;
|
||||
R_TRY(impl::HasDirectory(&has_dir, root->path));
|
||||
R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::OpenContentStorage(sf::Out<std::shared_ptr<IContentStorage>> out, StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
if (hos::GetVersion() >= hos::Version_200) {
|
||||
/* Obtain the content storage if already active. */
|
||||
R_UNLESS(root->content_storage, GetContentStorageNotActiveResult(storage_id));
|
||||
} else {
|
||||
/* 1.0.0 activates content storages as soon as they are opened. */
|
||||
if (!root->content_storage) {
|
||||
R_TRY(this->ActivateContentStorage(storage_id));
|
||||
}
|
||||
}
|
||||
|
||||
out.SetValue(std::shared_ptr<IContentStorage>(root->content_storage));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
if (hos::GetVersion() >= hos::Version_200) {
|
||||
/* Obtain the content meta database if already active. */
|
||||
R_UNLESS(root->content_meta_database, GetContentMetaDatabaseNotActiveResult(storage_id));
|
||||
} else {
|
||||
/* 1.0.0 activates content meta databases as soon as they are opened. */
|
||||
if (!root->content_meta_database) {
|
||||
R_TRY(this->ActivateContentMetaDatabase(storage_id));
|
||||
}
|
||||
}
|
||||
|
||||
out.SetValue(std::shared_ptr<IContentMetaDatabase>(root->content_meta_database));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CloseContentStorageForcibly(StorageId storage_id) {
|
||||
return this->InactivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CloseContentMetaDatabaseForcibly(StorageId storage_id) {
|
||||
return this->InactivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Disable and unmount content meta database root. */
|
||||
R_TRY(this->InactivateContentMetaDatabase(storage_id));
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Delete save data for the content meta database root. */
|
||||
return fs::DeleteSaveData(root->info.space_id, root->info.id);
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::ActivateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Check if the storage is already activated. */
|
||||
R_SUCCEED_IF(root->content_storage != nullptr);
|
||||
|
||||
/* Mount based on the storage type. */
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
fs::GameCardHandle handle;
|
||||
R_TRY(fs::GetGameCardHandle(std::addressof(handle)));
|
||||
R_TRY(fs::MountGameCardPartition(root->mount_name, handle, fs::GameCardPartition::Secure));
|
||||
} else {
|
||||
R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id));
|
||||
}
|
||||
|
||||
/* Unmount on failure. */
|
||||
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
|
||||
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
/* Game card content storage is read only. */
|
||||
auto content_storage = std::make_shared<ReadOnlyContentStorageImpl>();
|
||||
R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath));
|
||||
root->content_storage = std::move(content_storage);
|
||||
} else {
|
||||
/* Create a content storage. */
|
||||
auto content_storage = std::make_shared<ContentStorageImpl>();
|
||||
|
||||
/* Initialize content storage with an appropriate path function. */
|
||||
switch (storage_id) {
|
||||
case StorageId::BuiltInSystem:
|
||||
R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(this->rights_id_cache)));
|
||||
break;
|
||||
case StorageId::SdCard:
|
||||
R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, true, std::addressof(this->rights_id_cache)));
|
||||
break;
|
||||
default:
|
||||
R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, false, std::addressof(this->rights_id_cache)));
|
||||
break;
|
||||
}
|
||||
|
||||
root->content_storage = std::move(content_storage);
|
||||
}
|
||||
|
||||
/* Prevent unmounting. */
|
||||
mount_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InactivateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content storage root. */
|
||||
ContentStorageRoot *root;
|
||||
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
|
||||
|
||||
/* Disable and unmount the content storage, if present. */
|
||||
if (root->content_storage) {
|
||||
/* N doesn't bother checking the result of this */
|
||||
root->content_storage->DisableForcibly();
|
||||
root->content_storage = nullptr;
|
||||
fs::Unmount(root->mount_name);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Already activated. */
|
||||
R_SUCCEED_IF(root->content_meta_database != nullptr);
|
||||
|
||||
/* Make a new kvs. */
|
||||
root->kvs.emplace();
|
||||
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
/* Initialize the key value store. */
|
||||
R_TRY(root->kvs->Initialize(root->max_content_metas));
|
||||
|
||||
/* Create an on memory content meta database for game cards. */
|
||||
root->content_meta_database = std::make_shared<OnMemoryContentMetaDatabaseImpl>(std::addressof(*root->kvs));
|
||||
} else {
|
||||
/* Mount save data for this root. */
|
||||
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
|
||||
|
||||
/* Unmount on failure. */
|
||||
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
|
||||
|
||||
/* Initialize and load the key value store from the filesystem. */
|
||||
R_TRY(root->kvs->Initialize(root->path, root->max_content_metas));
|
||||
R_TRY(root->kvs->Load());
|
||||
|
||||
/* Create the content meta database. */
|
||||
root->content_meta_database = std::make_shared<ContentMetaDatabaseImpl>(std::addressof(*root->kvs), root->mount_name);
|
||||
mount_guard.Cancel();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock lk(this->mutex);
|
||||
|
||||
/* Obtain the content meta database root. */
|
||||
ContentMetaDatabaseRoot *root;
|
||||
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
|
||||
|
||||
/* Disable the content meta database, if present. */
|
||||
if (root->content_meta_database) {
|
||||
/* N doesn't bother checking the result of this */
|
||||
root->content_meta_database->DisableForcibly();
|
||||
root->content_meta_database = nullptr;
|
||||
root->kvs = std::nullopt;
|
||||
|
||||
/* Also unmount, except in the case of game cards. */
|
||||
if (storage_id != StorageId::GameCard) {
|
||||
fs::Unmount(root->mount_name);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentManagerImpl::InvalidateRightsIdCache() {
|
||||
this->rights_id_cache.Invalidate();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
101
libraries/libstratosphere/source/ncm/ncm_content_meta.cpp
Normal file
101
libraries/libstratosphere/source/ncm/ncm_content_meta.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void ConvertPackageContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const PackagedContentMetaHeader &src) {
|
||||
/* Clear destination. */
|
||||
*dst = {};
|
||||
|
||||
/* Set converted fields. */
|
||||
dst->extended_header_size = src.extended_header_size;
|
||||
dst->content_meta_count = src.content_meta_count;
|
||||
dst->content_count = src.content_meta_count;
|
||||
dst->attributes = src.attributes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
size_t PackagedContentMetaReader::CountDeltaFragments() const {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < this->GetContentCount(); i++) {
|
||||
if (this->GetContentInfo(i)->GetType() == ContentType::DeltaFragment) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t PackagedContentMetaReader::CalculateConvertContentMetaSize() const {
|
||||
const auto *header = this->GetHeader();
|
||||
return this->CalculateSizeImpl<ContentMetaHeader, ContentInfo>(header->extended_header_size, header->content_count + 1, header->content_meta_count, 0, false);
|
||||
}
|
||||
|
||||
void PackagedContentMetaReader::ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta) {
|
||||
/* Ensure we have enough space to convert. */
|
||||
AMS_ABORT_UNLESS(size >= this->CalculateConvertContentMetaSize());
|
||||
|
||||
/* Prepare for conversion. */
|
||||
const auto *packaged_header = this->GetHeader();
|
||||
uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst);
|
||||
|
||||
/* Convert the header. */
|
||||
ContentMetaHeader header;
|
||||
ConvertPackageContentMetaHeaderToContentMetaHeader(std::addressof(header), *packaged_header);
|
||||
header.content_count += 1;
|
||||
|
||||
/* Don't include deltas. */
|
||||
if (packaged_header->type == ContentMetaType::Patch) {
|
||||
header.content_count -= this->CountDeltaFragments();
|
||||
}
|
||||
|
||||
/* Copy the header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header));
|
||||
dst_addr += sizeof(header);
|
||||
|
||||
/* Copy the extended header. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size);
|
||||
dst_addr += packaged_header->extended_header_size;
|
||||
|
||||
/* Copy the top level meta. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta));
|
||||
dst_addr += sizeof(meta);
|
||||
|
||||
/* Copy content infos. */
|
||||
for (size_t i = 0; i < this->GetContentCount(); i++) {
|
||||
/* Don't copy any delta fragments. */
|
||||
if (packaged_header->type == ContentMetaType::Patch) {
|
||||
if (this->GetContentInfo(i)->GetType() == ContentType::DeltaFragment) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the current info. */
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(this->GetContentInfo(i)->info), sizeof(ContentInfo));
|
||||
dst_addr += sizeof(ContentInfo);
|
||||
}
|
||||
|
||||
/* Copy content meta infos. */
|
||||
for (size_t i = 0; i < this->GetContentMetaCount(); i++) {
|
||||
std::memcpy(reinterpret_cast<void *>(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo));
|
||||
dst_addr += sizeof(ContentMetaInfo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) const {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Find the meta key. */
|
||||
const auto it = this->kvs->lower_bound(key);
|
||||
R_UNLESS(it != this->kvs->end(), ncm::ResultContentMetaNotFound());
|
||||
R_UNLESS(it->GetKey().id == key.id, ncm::ResultContentMetaNotFound());
|
||||
|
||||
const auto found_key = it->GetKey();
|
||||
|
||||
/* Create a reader for this content meta. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, found_key));
|
||||
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Find the content info. */
|
||||
const ContentInfo *content_info = nullptr;
|
||||
if (id_offset) {
|
||||
content_info = reader.GetContentInfo(type, *id_offset);
|
||||
} else {
|
||||
content_info = reader.GetContentInfo(type);
|
||||
}
|
||||
R_UNLESS(content_info != nullptr, ncm::ResultContentNotFound());
|
||||
|
||||
/* Save output. */
|
||||
*out = content_info->content_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Set(const ContentMetaKey &key, sf::InBuffer value) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->kvs->Set(key, value.GetPointer(), value.GetSize());
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Get the entry from our key-value store. */
|
||||
size_t size;
|
||||
R_TRY_CATCH(this->kvs->Get(std::addressof(size), out_value.GetPointer(), out_value.GetSize(), key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
out_size.SetValue(size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Remove(const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
R_TRY_CATCH(this->kvs->Remove(key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) {
|
||||
return this->GetContentIdImpl(out_content_id.GetPointer(), key, type, std::nullopt);
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::ListContentInfo(sf::Out<s32> out_count, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) {
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the given key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Read content infos from the given offset up to the given count. */
|
||||
size_t count;
|
||||
for (count = 0; count < out_info.GetSize() && count + offset < reader.GetContentCount(); count++) {
|
||||
out_info[count] = *reader.GetContentInfo(offset + count);
|
||||
}
|
||||
|
||||
out_count.SetValue(count);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto &entry : *this->kvs) {
|
||||
const ContentMetaKey key = entry.GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!((meta_type == ContentMetaType::Unknown || key.type == meta_type) && (min <= key.id && key.id <= max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If application id is present, check if it matches the filter. */
|
||||
if (application_id != InvalidApplicationId) {
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Ensure application id matches, if present. */
|
||||
if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the entry to the output buffer. */
|
||||
if (entries_written < out_info.GetSize()) {
|
||||
out_info[entries_written++] = key;
|
||||
}
|
||||
entries_total++;
|
||||
}
|
||||
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
std::optional<ContentMetaKey> found_key = std::nullopt;
|
||||
|
||||
/* Find the last key with the desired program id. */
|
||||
for (auto entry = this->kvs->lower_bound(ContentMetaKey::MakeUnknownType(id, 0)); entry != this->kvs->end(); entry++) {
|
||||
/* No further entries will match the program id, discontinue. */
|
||||
if (entry->GetKey().id != id) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* We are only interested in keys with the Full content install type. */
|
||||
if (entry->GetKey().install_type == ContentInstallType::Full) {
|
||||
found_key = entry->GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the key is absent. */
|
||||
R_UNLESS(found_key, ncm::ResultContentMetaNotFound());
|
||||
|
||||
out_key.SetValue(*found_key);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType type) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto &entry : *this->kvs) {
|
||||
const ContentMetaKey key = entry.GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!(type == ContentMetaType::Unknown || key.type == type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if the entry has an application id. */
|
||||
ContentMetaReader reader(entry.GetValuePointer(), entry.GetValueSize());
|
||||
|
||||
if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id) {
|
||||
/* Write the entry to the output buffer. */
|
||||
if (entries_written < out_keys.GetSize()) {
|
||||
out_keys[entries_written++] = { key, *entry_application_id };
|
||||
}
|
||||
entries_total++;
|
||||
}
|
||||
}
|
||||
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Has(sf::Out<bool> out, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
*out = false;
|
||||
|
||||
/* Check if key is present. */
|
||||
size_t size;
|
||||
R_TRY_CATCH(this->kvs->GetValueSize(&size, key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ResultSuccess());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
*out = false;
|
||||
|
||||
/* Check if keys are present. */
|
||||
for (size_t i = 0; i < keys.GetSize(); i++) {
|
||||
/* Check if we have the current key. */
|
||||
bool has;
|
||||
R_TRY(this->Has(std::addressof(has), keys[i]));
|
||||
|
||||
/* If we don't, then we can early return because we don't have all. */
|
||||
R_SUCCEED_IF(!has);
|
||||
}
|
||||
|
||||
*out = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Determine the content meta size for the key. */
|
||||
size_t size;
|
||||
R_TRY(this->GetContentMetaSize(&size, key));
|
||||
|
||||
out_size.SetValue(size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Only applications and patches have a required system version. */
|
||||
R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch, ncm::ResultInvalidContentMetaKey());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Obtain the required system version. */
|
||||
switch (key.type) {
|
||||
case ContentMetaType::Application:
|
||||
out_version.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_system_version);
|
||||
break;
|
||||
case ContentMetaType::Patch:
|
||||
out_version.SetValue(reader.GetExtendedHeader<PatchMetaExtendedHeader>()->required_system_version);
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetPatchId(sf::Out<PatchId> out_patch_id, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Only applications can have patches. */
|
||||
R_UNLESS(key.type == ContentMetaType::Application, ncm::ResultInvalidContentMetaKey());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Obtain the patch id. */
|
||||
out_patch_id.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->patch_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
R_UNLESS(out_orphaned.GetSize() >= content_ids.GetSize(), ncm::ResultBufferInsufficient());
|
||||
|
||||
/* Default to orphaned for all content ids. */
|
||||
for (size_t i = 0; i < out_orphaned.GetSize(); i++) {
|
||||
out_orphaned[i] = true;
|
||||
}
|
||||
|
||||
auto IsOrphanedContent = [](const sf::InArray<ContentId> &list, const ncm::ContentId &id) ALWAYS_INLINE_LAMBDA {
|
||||
/* Check if any input content ids match our found content id. */
|
||||
for (size_t i = 0; i < list.GetSize(); i++) {
|
||||
if (list[i] == id) {
|
||||
return std::make_optional(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: C++20 (gcc 10) fixes ALWAYS_INLINE_LAMBDA in conjunction with trailing return types. */
|
||||
/* This should be changed to std::nullopt once possible. */
|
||||
return std::optional<size_t>(std::nullopt);
|
||||
};
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto &entry : *this->kvs) {
|
||||
ContentMetaReader reader(entry.GetValuePointer(), entry.GetValueSize());
|
||||
|
||||
/* Check if any of this entry's content infos matches one of the content ids for lookup. */
|
||||
/* If they do, then the content id isn't orphaned. */
|
||||
for (size_t i = 0; i < reader.GetContentCount(); i++) {
|
||||
if (auto found = IsOrphanedContent(content_ids, reader.GetContentInfo(i)->GetId()); found) {
|
||||
out_orphaned[*found] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::Commit() {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Save and commit. */
|
||||
R_TRY(this->kvs->Save());
|
||||
return fs::CommitSaveData(this->mount_name);
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) {
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Optimistically suppose that we will find the content. */
|
||||
out.SetValue(true);
|
||||
|
||||
/* Check if any content infos contain a matching id. */
|
||||
for (size_t i = 0; i < reader.GetContentCount(); i++) {
|
||||
R_SUCCEED_IF(content_id == reader.GetContentInfo(i)->GetId());
|
||||
}
|
||||
|
||||
/* We didn't find a content info. */
|
||||
out.SetValue(false);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) {
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Read content meta infos from the given offset up to the given count. */
|
||||
size_t count;
|
||||
for (count = 0; count < out_meta_info.GetSize() && count + offset <= reader.GetContentMetaCount(); count++) {
|
||||
out_meta_info[count] = *reader.GetContentMetaInfo(count + offset);
|
||||
}
|
||||
|
||||
/* Set the ouput value. */
|
||||
out_entries_written.SetValue(count);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Set the ouput value. */
|
||||
out_attributes.SetValue(reader.GetHeader()->attributes);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Get the required version. */
|
||||
u32 required_version;
|
||||
switch (key.type) {
|
||||
case ContentMetaType::AddOnContent:
|
||||
required_version = reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->required_application_version;
|
||||
break;
|
||||
case ContentMetaType::Application:
|
||||
/* As of 9.0.0, applications can be dependent on a specific base application version. */
|
||||
AMS_ABORT_UNLESS(hos::GetVersion() >= hos::Version_900);
|
||||
required_version = reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_application_version;
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Set the ouput value. */
|
||||
out_version.SetValue(required_version);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseImpl::GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) {
|
||||
return this->GetContentIdImpl(out_content_id.GetPointer(), key, type, std::make_optional(id_offset));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_meta_database_impl_base.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentMetaDatabaseImpl : public ContentMetaDatabaseImplBase {
|
||||
public:
|
||||
ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs, mount_name) { /* ... */ }
|
||||
ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs) : ContentMetaDatabaseImplBase(kvs) { /* ... */ }
|
||||
private:
|
||||
/* Helpers. */
|
||||
Result GetContentIdImpl(ContentId *out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) const;
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override;
|
||||
virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) override;
|
||||
virtual Result Remove(const ContentMetaKey &key) override;
|
||||
virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) override;
|
||||
virtual Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) override;
|
||||
virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override;
|
||||
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override;
|
||||
virtual Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) override;
|
||||
virtual Result Has(sf::Out<bool> out, const ContentMetaKey &key) override;
|
||||
virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) override;
|
||||
virtual Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) override;
|
||||
virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override;
|
||||
virtual Result GetPatchId(sf::Out<PatchId> out_patch_id, const ContentMetaKey &key) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;
|
||||
virtual Result Commit() override;
|
||||
virtual Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) override;
|
||||
virtual Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) override;
|
||||
virtual Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) override;
|
||||
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override;
|
||||
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentMetaDatabaseImplBase : public IContentMetaDatabase {
|
||||
NON_COPYABLE(ContentMetaDatabaseImplBase);
|
||||
NON_MOVEABLE(ContentMetaDatabaseImplBase);
|
||||
protected:
|
||||
using ContentMetaKeyValueStore = ams::kvdb::MemoryKeyValueStore<ContentMetaKey>;
|
||||
protected:
|
||||
ContentMetaKeyValueStore *kvs;
|
||||
char mount_name[fs::MountNameLengthMax + 1];
|
||||
bool disabled;
|
||||
protected:
|
||||
ContentMetaDatabaseImplBase(ContentMetaKeyValueStore *kvs) : kvs(kvs), disabled(false) { /* ... */ }
|
||||
|
||||
ContentMetaDatabaseImplBase(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs) {
|
||||
std::strcpy(this->mount_name, mount_name);
|
||||
}
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
R_UNLESS(!this->disabled, ncm::ResultInvalidContentMetaDatabase());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result GetContentMetaSize(size_t *out, const ContentMetaKey &key) const {
|
||||
R_TRY_CATCH(this->kvs->GetValueSize(out, key)) {
|
||||
R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result GetContentMetaPointer(const void **out_value_ptr, size_t *out_size, const ContentMetaKey &key) const {
|
||||
R_TRY(this->GetContentMetaSize(out_size, key));
|
||||
return this->kvs->GetValuePointer(reinterpret_cast<const ContentMetaHeader **>(out_value_ptr), key);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
const char *GetContentMetaTypeString(ContentMetaType type) {
|
||||
switch (type) {
|
||||
case ContentMetaType::Application: return "Application";
|
||||
case ContentMetaType::Patch: return "Patch";
|
||||
case ContentMetaType::AddOnContent: return "AddOnContent";
|
||||
default: return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
using FilePathString = kvdb::BoundedString<64>;
|
||||
|
||||
bool IsContentMetaFileName(const char *name) {
|
||||
return impl::PathView(name).HasSuffix(".cnmt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ReadContentMetaPath(AutoBuffer *out, const char *path) {
|
||||
/* Mount the content. */
|
||||
auto mount_name = impl::CreateUniqueMountName();
|
||||
R_TRY(fs::MountContent(mount_name.str, path, fs::ContentType_Meta));
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name.str); };
|
||||
|
||||
/* Open the root directory. */
|
||||
auto root_path = impl::GetRootDirectoryPath(mount_name);
|
||||
fs::DirectoryHandle dir;
|
||||
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path.str, fs::OpenDirectoryMode_File));
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||
|
||||
/* Loop directory reading until we find the entry we're looking for. */
|
||||
while (true) {
|
||||
/* Read one entry, and finish when we fail to read. */
|
||||
fs::DirectoryEntry entry;
|
||||
s64 num_read;
|
||||
R_TRY(fs::ReadDirectory(std::addressof(num_read), std::addressof(entry), dir, 1));
|
||||
if (num_read == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* If this is the content meta file, parse it. */
|
||||
if (IsContentMetaFileName(entry.name)) {
|
||||
/* Create the file path. */
|
||||
FilePathString file_path(root_path.str);
|
||||
file_path.Append(entry.name);
|
||||
|
||||
/* Open the content meta file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), file_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Get the meta size. */
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
const size_t meta_size = static_cast<size_t>(file_size);
|
||||
|
||||
/* Create a buffer for the meta. */
|
||||
R_TRY(out->Initialize(meta_size));
|
||||
|
||||
/* Read the meta into the buffer. */
|
||||
return fs::ReadFile(file, 0, out->Get(), meta_size);
|
||||
}
|
||||
}
|
||||
|
||||
return ncm::ResultContentMetaNotFound();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,760 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_storage_impl.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline const char * const BaseContentDirectory = "/registered";
|
||||
|
||||
void MakeBaseContentDirectoryPath(PathString *out, const char *root_path) {
|
||||
out->SetFormat("%s%s", root_path, BaseContentDirectory);
|
||||
}
|
||||
|
||||
void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
func(out, id, path);
|
||||
}
|
||||
|
||||
Result EnsureContentDirectory(ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), id, func, root_path);
|
||||
return impl::EnsureParentDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
Result DeleteContentFile(ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
/* Create the content path. */
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), id, func, root_path);
|
||||
|
||||
/* Delete the content. */
|
||||
R_TRY_CATCH(fs::DeleteFile(path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultContentNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) {
|
||||
/* If the level is zero, we're done. */
|
||||
R_SUCCEED_IF(max_level <= 0);
|
||||
|
||||
/* On 1.0.0, NotRequireFileSize was not a valid open mode. */
|
||||
const auto open_dir_mode = hos::GetVersion() >= hos::Version_200 ? (fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize) : (fs::OpenDirectoryMode_All);
|
||||
|
||||
/* Retry traversal upon request. */
|
||||
bool retry_dir_read = true;
|
||||
while (retry_dir_read) {
|
||||
retry_dir_read = false;
|
||||
|
||||
/* Open the directory at the given path. All entry types are allowed. */
|
||||
fs::DirectoryHandle dir;
|
||||
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, open_dir_mode));
|
||||
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||
|
||||
while (true) {
|
||||
/* Read a single directory entry. */
|
||||
fs::DirectoryEntry entry;
|
||||
s64 entry_count;
|
||||
R_TRY(fs::ReadDirectory(std::addressof(entry_count), std::addressof(entry), dir, 1));
|
||||
|
||||
/* Directory has no entries to process. */
|
||||
if (entry_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Path of the current entry. */
|
||||
PathString current_path;
|
||||
current_path.SetFormat("%s/%s", root_path, entry.name);
|
||||
|
||||
/* Call the process function. */
|
||||
bool should_continue = true;
|
||||
bool should_retry_dir_read = false;
|
||||
R_TRY(f(&should_continue, &should_retry_dir_read, current_path, entry));
|
||||
|
||||
/* If the provided function wishes to terminate immediately, we should respect it. */
|
||||
if (!should_continue) {
|
||||
*out_should_continue = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Mark for retry. */
|
||||
if (should_retry_dir_read) {
|
||||
retry_dir_read = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If the entry is a directory, recurse. */
|
||||
if (entry.type == fs::DirectoryEntryType_Directory) {
|
||||
R_TRY(TraverseDirectory(std::addressof(should_continue), current_path, max_level - 1, f));
|
||||
|
||||
if (!should_continue) {
|
||||
*out_should_continue = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
|
||||
template<typename F>
|
||||
Result TraverseDirectory(const char *root_path, int max_level, F f) {
|
||||
bool should_continue = false;
|
||||
return TraverseDirectory(std::addressof(should_continue), root_path, max_level, f);
|
||||
}
|
||||
|
||||
bool IsContentPath(const char *path) {
|
||||
impl::PathView view(path);
|
||||
|
||||
/* Ensure nca suffix. */
|
||||
if (!view.HasSuffix(".nca")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* File name should be the size of a content id plus the nca file extension. */
|
||||
auto file_name = view.GetFileName();
|
||||
if (file_name.length() != ContentIdStringLength + 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure file name is comprised of hex characters. */
|
||||
for (size_t i = 0; i < ContentIdStringLength; i++) {
|
||||
if (!std::isxdigit(static_cast<unsigned char>(file_name[i]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPlaceHolderPath(const char *path) {
|
||||
return IsContentPath(path);
|
||||
}
|
||||
|
||||
Result CleanDirectoryRecursively(const PathString &path) {
|
||||
if (hos::GetVersion() >= hos::Version_300) {
|
||||
R_TRY(fs::CleanDirectoryRecursively(path));
|
||||
} else {
|
||||
/* CleanDirectoryRecursively didn't exist on < 3.0.0, so we will polyfill it. */
|
||||
/* We'll delete the directory, then recreate it. */
|
||||
R_TRY(fs::DeleteDirectoryRecursively(path));
|
||||
R_TRY(fs::CreateDirectory(path));
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ContentStorageImpl::~ContentStorageImpl() {
|
||||
this->InvalidateFileCache();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::InitializeBase(const char *root_path) {
|
||||
PathString path;
|
||||
|
||||
/* Create the content directory. */
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::EnsureDirectoryRecursively(path));
|
||||
|
||||
/* Create the placeholder directory. */
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
|
||||
return impl::EnsureDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::CleanupBase(const char *root_path) {
|
||||
PathString path;
|
||||
|
||||
/* Create the content directory. */
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(CleanDirectoryRecursively(path));
|
||||
|
||||
/* Create the placeholder directory. */
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
|
||||
return CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::VerifyBase(const char *root_path) {
|
||||
PathString path;
|
||||
|
||||
/* Check if root directory exists. */
|
||||
bool has_dir;
|
||||
R_TRY(impl::HasDirectory(std::addressof(has_dir), root_path));
|
||||
R_UNLESS(has_dir, ncm::ResultContentStorageBaseNotFound());
|
||||
|
||||
/* Check if content directory exists. */
|
||||
bool has_registered;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::HasDirectory(std::addressof(has_registered), path));
|
||||
|
||||
/* Check if placeholder directory exists. */
|
||||
bool has_placeholder;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
|
||||
R_TRY(impl::HasDirectory(std::addressof(has_placeholder), path));
|
||||
|
||||
/* Convert findings to results. */
|
||||
R_UNLESS(has_registered || has_placeholder, ncm::ResultContentStorageBaseNotFound());
|
||||
R_UNLESS(has_registered, ncm::ResultInvalidContentStorageBase());
|
||||
R_UNLESS(has_placeholder, ncm::ResultInvalidContentStorageBase());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void ContentStorageImpl::InvalidateFileCache() {
|
||||
if (this->cached_content_id != InvalidContentId) {
|
||||
fs::CloseFile(this->cached_file_handle);
|
||||
this->cached_content_id = InvalidContentId;
|
||||
}
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) {
|
||||
/* If the file is the currently cached one, we've nothing to do. */
|
||||
R_SUCCEED_IF(this->cached_content_id == content_id);
|
||||
|
||||
/* Close any cached file. */
|
||||
this->InvalidateFileCache();
|
||||
|
||||
/* Create the content path. */
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file and store to the cache. */
|
||||
R_TRY_CATCH(fs::OpenFile(&this->cached_file_handle, path, fs::OpenMode_Read)) {
|
||||
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
this->cached_content_id = content_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Check paths exists for this content storage. */
|
||||
R_TRY(VerifyBase(path));
|
||||
|
||||
/* Initialize members. */
|
||||
this->root_path = PathString(path);
|
||||
this->make_content_path_func = content_path_func;
|
||||
this->placeholder_accessor.Initialize(std::addressof(this->root_path), placeholder_path_func, delay_flush);
|
||||
this->rights_id_cache = rights_id_cache;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
out.SetValue({util::GenerateUuid()});
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
R_TRY(EnsureContentDirectory(content_id, this->make_content_path_func, this->root_path));
|
||||
return this->placeholder_accessor.CreatePlaceHolderFile(placeholder_id, size);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.DeletePlaceHolderFile(placeholder_id);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.MakePath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Check if placeholder file exists. */
|
||||
bool has = false;
|
||||
R_TRY(impl::HasFile(&has, placeholder_path));
|
||||
out.SetValue(has);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize());
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
|
||||
this->InvalidateFileCache();
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Move the placeholder to the content path. */
|
||||
R_TRY_CATCH(fs::RenameFile(placeholder_path, content_path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Delete(ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
this->InvalidateFileCache();
|
||||
return DeleteContentFile(content_id, this->make_content_path_func, this->root_path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Check if the content file exists. */
|
||||
bool has = false;
|
||||
R_TRY(impl::HasFile(&has, content_path));
|
||||
out.SetValue(has);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Substitute our mount name for the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path));
|
||||
|
||||
out.SetValue(common_path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Substitute our mount name for the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path));
|
||||
|
||||
out.SetValue(common_path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::CleanupAllPlaceHolder() {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Clear the cache. */
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
|
||||
/* Obtain the placeholder base directory path. */
|
||||
PathString placeholder_dir;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path);
|
||||
|
||||
/* Cleanup the placeholder base directory. */
|
||||
CleanDirectoryRecursively(placeholder_dir);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the placeholder base directory path. */
|
||||
PathString placeholder_dir;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path);
|
||||
|
||||
const size_t max_entries = out_buf.GetSize();
|
||||
size_t entry_count = 0;
|
||||
|
||||
/* Traverse the placeholder base directory finding valid placeholder files. */
|
||||
R_TRY(TraverseDirectory(placeholder_dir, placeholder_accessor.GetHierarchicalDirectoryDepth(), [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result {
|
||||
*should_continue = true;
|
||||
*should_retry_dir_read = false;
|
||||
|
||||
/* We are only looking for files. */
|
||||
if (entry.type == fs::DirectoryEntryType_File) {
|
||||
R_UNLESS(entry_count <= max_entries, ncm::ResultBufferInsufficient());
|
||||
|
||||
/* Get the placeholder id from the filename. */
|
||||
PlaceHolderId placeholder_id;
|
||||
R_TRY(PlaceHolderAccessor::GetPlaceHolderIdFromFileName(std::addressof(placeholder_id), entry.name));
|
||||
|
||||
out_buf[entry_count++] = placeholder_id;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<s32>(entry_count));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetContentCount(sf::Out<s32> out_count) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content base directory path. */
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func);
|
||||
size_t count = 0;
|
||||
|
||||
/* Traverse the content base directory finding all files. */
|
||||
R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result {
|
||||
*should_continue = true;
|
||||
*should_retry_dir_read = false;
|
||||
|
||||
/* Increment the count for each file found. */
|
||||
if (entry.type == fs::DirectoryEntryType_File) {
|
||||
count++;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<s32>(count));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) {
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Obtain the content base directory path. */
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func);
|
||||
size_t entry_count = 0;
|
||||
|
||||
/* Traverse the content base directory finding all valid content. */
|
||||
R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) {
|
||||
*should_retry_dir_read = false;
|
||||
*should_continue = true;
|
||||
|
||||
/* We have nothing to do if not working with a file. */
|
||||
if (entry.type != fs::DirectoryEntryType_File) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Skip entries until we reach the start offset. */
|
||||
if (offset > 0) {
|
||||
--offset;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* We don't necessarily expect to be able to completely fill the output buffer. */
|
||||
if (entry_count >= out_buf.GetSize()) {
|
||||
*should_continue = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
auto content_id = GetContentIdFromString(entry.name, std::strlen(entry.name));
|
||||
if (content_id) {
|
||||
out_buf[entry_count++] = *content_id;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<s32>(entry_count));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), content_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Obtain the size of the content. */
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
this->InvalidateFileCache();
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Close any cached file. */
|
||||
this->InvalidateFileCache();
|
||||
|
||||
/* Ensure the future content directory exists. */
|
||||
R_TRY(EnsureContentDirectory(new_content_id, this->make_content_path_func, this->root_path));
|
||||
|
||||
/* Ensure the destination placeholder directory exists. */
|
||||
R_TRY(this->placeholder_accessor.EnsurePlaceHolderDirectory(placeholder_id));
|
||||
|
||||
/* Obtain the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Make the old content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), old_content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Move the content to the placeholder path. */
|
||||
R_TRY_CATCH(fs::RenameFile(content_path, placeholder_path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Create the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
R_TRY(this->OpenContentIdFile(content_id));
|
||||
|
||||
/* Read from the requested offset up to the requested size. */
|
||||
return fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize());
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
/* Obtain the regular rights id for the placeholder id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(this->GetRightsIdFromPlaceHolderId(&rights_id, placeholder_id));
|
||||
|
||||
/* Output the fs rights id. */
|
||||
out_rights_id.SetValue(rights_id.id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Get the placeholder path. */
|
||||
Path path;
|
||||
R_TRY(this->GetPlaceHolderPath(std::addressof(path), placeholder_id));
|
||||
|
||||
/* Get the rights id for the placeholder id. */
|
||||
return GetRightsId(out_rights_id.GetPointer(), path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) {
|
||||
/* Obtain the regular rights id for the content id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(this->GetRightsIdFromContentId(&rights_id, content_id));
|
||||
|
||||
/* Output the fs rights id. */
|
||||
out_rights_id.SetValue(rights_id.id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Attempt to obtain the rights id from the cache. */
|
||||
if (this->rights_id_cache->Find(out_rights_id.GetPointer(), content_id)) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Get the path of the content. */
|
||||
Path path;
|
||||
R_TRY(this->GetPath(std::addressof(path), content_id));
|
||||
|
||||
/* Obtain the rights id for the content. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(GetRightsId(std::addressof(rights_id), path));
|
||||
|
||||
/* Store the rights id to the cache. */
|
||||
this->rights_id_cache->Store(content_id, rights_id);
|
||||
|
||||
out_rights_id.SetValue(rights_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* This command is for development hardware only. */
|
||||
AMS_ABORT_UNLESS(spl::IsDevelopmentHardware());
|
||||
|
||||
/* Close any cached file. */
|
||||
this->InvalidateFileCache();
|
||||
|
||||
/* Make the content path. */
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), path.Get(), fs::OpenMode_Write));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Write the provided data to the file. */
|
||||
return fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) {
|
||||
return fs::GetFreeSpaceSize(out_size.GetPointer(), this->root_path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) {
|
||||
return fs::GetTotalSpaceSize(out_size.GetPointer(), this->root_path);
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::FlushPlaceHolder() {
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out_size, PlaceHolderId placeholder_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Attempt to get the placeholder file size. */
|
||||
bool found = false;
|
||||
s64 file_size = 0;
|
||||
R_TRY(this->placeholder_accessor.TryGetPlaceHolderFileSize(std::addressof(found), std::addressof(file_size), placeholder_id));
|
||||
|
||||
/* Set the output if placeholder file is found. */
|
||||
if (found) {
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Get the path of the placeholder. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Open the placeholder file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), placeholder_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Get the size of the placeholder file. */
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::RepairInvalidFileAttribute() {
|
||||
/* Callback for TraverseDirectory */
|
||||
using PathChecker = bool (*)(const char *);
|
||||
PathChecker path_checker = nullptr;
|
||||
|
||||
/* Set the archive bit appropriately for content/placeholders. */
|
||||
auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) {
|
||||
*should_retry_dir_read = false;
|
||||
*should_continue = true;
|
||||
|
||||
if (entry.type == fs::DirectoryEntryType_Directory) {
|
||||
if (path_checker(current_path)) {
|
||||
if (R_SUCCEEDED(fs::SetConcatenationFileAttribute(current_path))) {
|
||||
*should_retry_dir_read = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
};
|
||||
|
||||
/* Fix content. */
|
||||
{
|
||||
path_checker = IsContentPath;
|
||||
PathString path;
|
||||
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
|
||||
}
|
||||
|
||||
/* Fix placeholders. */
|
||||
this->placeholder_accessor.InvalidateAll();
|
||||
{
|
||||
path_checker = IsPlaceHolderPath;
|
||||
PathString path;
|
||||
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), this->root_path);
|
||||
|
||||
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Attempt to find the rights id in the cache. */
|
||||
if (this->rights_id_cache->Find(out_rights_id.GetPointer(), cache_content_id)) {
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Get the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Substitute mount name with the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path));
|
||||
|
||||
/* Get the rights id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(GetRightsId(&rights_id, common_path));
|
||||
this->rights_id_cache->Store(cache_content_id, rights_id);
|
||||
|
||||
/* Set output. */
|
||||
out_rights_id.SetValue(rights_id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "ncm_content_storage_impl_base.hpp"
|
||||
#include "ncm_placeholder_accessor.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentStorageImpl : public ContentStorageImplBase {
|
||||
protected:
|
||||
PlaceHolderAccessor placeholder_accessor;
|
||||
ContentId cached_content_id;
|
||||
fs::FileHandle cached_file_handle;
|
||||
RightsIdCache *rights_id_cache;
|
||||
public:
|
||||
static Result InitializeBase(const char *root_path);
|
||||
static Result CleanupBase(const char *root_path);
|
||||
static Result VerifyBase(const char *root_path);
|
||||
public:
|
||||
~ContentStorageImpl();
|
||||
|
||||
Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache);
|
||||
private:
|
||||
/* Helpers. */
|
||||
Result OpenContentIdFile(ContentId content_id);
|
||||
void InvalidateFileCache();
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
|
||||
virtual Result Delete(ContentId content_id) override;
|
||||
virtual Result Has(sf::Out<bool> out, ContentId content_id) override;
|
||||
virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override;
|
||||
virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result CleanupAllPlaceHolder() override;
|
||||
virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override;
|
||||
virtual Result GetContentCount(sf::Out<s32> out_count) override;
|
||||
virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) override;
|
||||
virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override;
|
||||
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result FlushPlaceHolder() override;
|
||||
virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result RepairInvalidFileAttribute() override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ContentStorageImplBase : public IContentStorage {
|
||||
NON_COPYABLE(ContentStorageImplBase);
|
||||
NON_MOVEABLE(ContentStorageImplBase);
|
||||
protected:
|
||||
PathString root_path;
|
||||
MakeContentPathFunction make_content_path_func;
|
||||
bool disabled;
|
||||
protected:
|
||||
ContentStorageImplBase() { /* ... */ }
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
R_UNLESS(!this->disabled, ncm::ResultInvalidContentStorage());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
static Result GetRightsId(ncm::RightsId *out_rights_id, const Path &path) {
|
||||
if (hos::GetVersion() >= hos::Version_300) {
|
||||
R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str));
|
||||
} else {
|
||||
R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), path.str));
|
||||
out_rights_id->key_generation = 0;
|
||||
}
|
||||
return ResultSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
158
libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp
Normal file
158
libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
Result EnsureDirectory(const char *path) {
|
||||
/* Create the path, and allow it to already exist. */
|
||||
R_TRY_CATCH(fs::CreateDirectory(path)) {
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ResultSuccess())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result EnsureDirectoryRecursivelyImpl(const char *path, bool create_last) {
|
||||
char work_buf[fs::EntryNameLengthMax];
|
||||
|
||||
/* Ensure the path is not too long. */
|
||||
const size_t len = std::strlen(path);
|
||||
R_UNLESS(len + 1 <= sizeof(work_buf), ResultAllocationFailed());
|
||||
|
||||
/* Copy in the path. */
|
||||
std::strncpy(work_buf, path, sizeof(work_buf));
|
||||
|
||||
/* Create all but the last directory. */
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i > 0 && fs::PathTool::IsSeparator(work_buf[i]) && !fs::PathTool::IsDriveSeparator(work_buf[i-1])) {
|
||||
work_buf[i] = fs::StringTraits::NullTerminator;
|
||||
R_TRY(EnsureDirectory(work_buf));
|
||||
work_buf[i] = fs::StringTraits::DirectorySeparator;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the last directory if requested. */
|
||||
if (create_last) {
|
||||
R_TRY(EnsureDirectory(path));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result HasEntry(bool *out, const char *path, fs::DirectoryEntryType type) {
|
||||
/* Set out to false initially. */
|
||||
*out = false;
|
||||
|
||||
/* Try to get the entry type. */
|
||||
fs::DirectoryEntryType entry_type;
|
||||
R_TRY_CATCH(fs::GetEntryType(std::addressof(entry_type), path)) {
|
||||
/* If the path doesn't exist, nothing has gone wrong. */
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultSuccess());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* We succeeded. */
|
||||
*out = entry_type == type;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
std::atomic<u32> g_mount_name_count;
|
||||
|
||||
}
|
||||
|
||||
Result HasFile(bool *out, const char *path) {
|
||||
return HasEntry(out, path, fs::DirectoryEntryType_File);
|
||||
}
|
||||
|
||||
Result HasDirectory(bool *out, const char *path) {
|
||||
return HasEntry(out, path, fs::DirectoryEntryType_Directory);
|
||||
}
|
||||
|
||||
Result EnsureDirectoryRecursively(const char *path) {
|
||||
return EnsureDirectoryRecursivelyImpl(path, true);
|
||||
}
|
||||
|
||||
Result EnsureParentDirectoryRecursively(const char *path) {
|
||||
return EnsureDirectoryRecursivelyImpl(path, false);
|
||||
}
|
||||
|
||||
bool PathView::HasPrefix(std::string_view prefix) const {
|
||||
return this->path.compare(0, prefix.length(), prefix) == 0;
|
||||
}
|
||||
|
||||
bool PathView::HasSuffix(std::string_view suffix) const {
|
||||
return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0;
|
||||
}
|
||||
|
||||
std::string_view PathView::GetFileName() const {
|
||||
auto pos = this->path.find_last_of("/");
|
||||
return pos != std::string_view::npos ? this->path.substr(pos + 1) : this->path;
|
||||
}
|
||||
|
||||
MountName CreateUniqueMountName() {
|
||||
MountName name = {};
|
||||
std::snprintf(name.str, sizeof(name.str), "@ncm%08x", g_mount_name_count.fetch_add(1));
|
||||
return name;
|
||||
}
|
||||
|
||||
RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name) {
|
||||
RootDirectoryPath path = {};
|
||||
std::snprintf(path.str, sizeof(path.str), "%s:/", mount_name.str);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
Result CopyFile(const char *dst_path, const char *src_path) {
|
||||
fs::FileHandle src_file, dst_file;
|
||||
|
||||
/* Open the source file and get its size. */
|
||||
R_TRY(fs::OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(src_file); };
|
||||
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), src_file));
|
||||
|
||||
/* Create the destination file. */
|
||||
R_TRY(fs::CreateFile(dst_path, file_size));
|
||||
|
||||
/* Open the destination file. */
|
||||
R_TRY(fs::OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(dst_file); };
|
||||
|
||||
/* Allocate a buffer with which to copy. */
|
||||
constexpr size_t BufferSize = 4_KB;
|
||||
AutoBuffer buffer;
|
||||
R_TRY(buffer.Initialize(BufferSize));
|
||||
|
||||
/* Repeatedly read until we've copied all the data. */
|
||||
s64 offset = 0;
|
||||
while (offset < file_size) {
|
||||
const size_t read_size = std::min(static_cast<size_t>(file_size - offset), buffer.GetSize());
|
||||
R_TRY(fs::ReadFile(src_file, offset, buffer.Get(), read_size));
|
||||
R_TRY(fs::WriteFile(dst_file, offset, buffer.Get(), read_size, fs::WriteOption::None));
|
||||
offset += read_size;
|
||||
}
|
||||
|
||||
/* Flush the destination file. */
|
||||
R_TRY(fs::FlushFile(dst_file));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
50
libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp
Normal file
50
libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm::impl {
|
||||
|
||||
Result HasFile(bool *out, const char *path);
|
||||
Result HasDirectory(bool *out, const char *path);
|
||||
|
||||
Result EnsureDirectoryRecursively(const char *path);
|
||||
Result EnsureParentDirectoryRecursively(const char *path);
|
||||
|
||||
Result CopyFile(const char *dst_path, const char *src_path);
|
||||
|
||||
class PathView {
|
||||
private:
|
||||
std::string_view path; /* Nintendo uses util::string_view here. */
|
||||
public:
|
||||
PathView(std::string_view p) : path(p) { /* ...*/ }
|
||||
bool HasPrefix(std::string_view prefix) const;
|
||||
bool HasSuffix(std::string_view suffix) const;
|
||||
std::string_view GetFileName() const;
|
||||
};
|
||||
|
||||
struct MountName {
|
||||
char str[fs::MountNameLengthMax + 1];
|
||||
};
|
||||
|
||||
struct RootDirectoryPath {
|
||||
char str[fs::MountNameLengthMax + 3]; /* mount name + :/ */
|
||||
};
|
||||
|
||||
MountName CreateUniqueMountName();
|
||||
RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name);
|
||||
|
||||
}
|
151
libraries/libstratosphere/source/ncm/ncm_make_path.cpp
Normal file
151
libraries/libstratosphere/source/ncm/ncm_make_path.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void MakeContentName(PathString *out, ContentId id) {
|
||||
out->SetFormat("%s.nca", GetContentIdString(id).data);
|
||||
}
|
||||
|
||||
void MakePlaceHolderName(PathString *out, PlaceHolderId id) {
|
||||
auto &bytes = id.uuid.data;
|
||||
char tmp[3];
|
||||
|
||||
/* Create a hex string from bytes. */
|
||||
for (size_t i = 0; i < sizeof(bytes); i++) {
|
||||
std::snprintf(tmp, util::size(tmp), "%02x", bytes[i]);
|
||||
out->Append(tmp);
|
||||
}
|
||||
|
||||
/* Append file extension. */
|
||||
out->Append(".nca");
|
||||
}
|
||||
|
||||
u16 Get16BitSha256HashPrefix(ContentId id) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
|
||||
return static_cast<u16>(hash[0]) | (static_cast<u16>(hash[1]) << 8);
|
||||
}
|
||||
|
||||
u8 Get8BitSha256HashPrefix(ContentId id) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
|
||||
return hash[0];
|
||||
}
|
||||
|
||||
u8 Get8BitSha256HashPrefix(PlaceHolderId id) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
|
||||
return hash[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%s", root_path, content_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Hash the content id. */
|
||||
const u16 hash = Get16BitSha256HashPrefix(content_id);
|
||||
const u32 hash_upper = (hash >> 10) & 0x3f;
|
||||
const u32 hash_lower = (hash >> 4) & 0x3f;
|
||||
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%08X/%s", root_path, hash_upper, hash_lower, content_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Hash the content id. */
|
||||
const u32 hash = (Get16BitSha256HashPrefix(content_id) >> 6) & 0x3FF;
|
||||
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%s", root_path, hash, content_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path) {
|
||||
/* Hash the content id. */
|
||||
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id));
|
||||
|
||||
/* Create the content name from the content id. */
|
||||
PathString content_name;
|
||||
MakeContentName(std::addressof(content_name), content_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%s", root_path, hash_byte, content_name.Get());
|
||||
}
|
||||
|
||||
size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func) {
|
||||
if (func == MakeFlatContentFilePath) {
|
||||
return 1;
|
||||
} else if (func == MakeSha256HierarchicalContentFilePath_ForFat4KCluster) {
|
||||
return 3;
|
||||
} else if (func == MakeSha256HierarchicalContentFilePath_ForFat16KCluster ||
|
||||
func == MakeSha256HierarchicalContentFilePath_ForFat32KCluster) {
|
||||
return 2;
|
||||
} else {
|
||||
AMS_ABORT();
|
||||
}
|
||||
}
|
||||
|
||||
void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path) {
|
||||
/* Create the placeholder name from the placeholder id. */
|
||||
PathString placeholder_name;
|
||||
MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%s", root_path, placeholder_name.Get());
|
||||
}
|
||||
|
||||
void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_path) {
|
||||
/* Hash the placeholder id. */
|
||||
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id));
|
||||
|
||||
/* Create the placeholder name from the placeholder id. */
|
||||
PathString placeholder_name;
|
||||
MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id);
|
||||
|
||||
/* Format the output path. */
|
||||
out->SetFormat("%s/%08X/%s", root_path, hash_byte, placeholder_name.Get());
|
||||
}
|
||||
|
||||
size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func) {
|
||||
if (func == MakeFlatPlaceHolderFilePath) {
|
||||
return 1;
|
||||
} else if (func == MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster) {
|
||||
return 2;
|
||||
} else {
|
||||
AMS_ABORT();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_on_memory_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) {
|
||||
/* NOTE: This function is *almost* identical to the ContentMetaDatabaseImpl equivalent. */
|
||||
/* The only difference is that the min max comparison is exclusive for OnMemoryContentMetaDatabaseImpl, */
|
||||
/* but inclusive for ContentMetaDatabaseImpl. This may or may not be a Nintendo bug? */
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* Iterate over all entries. */
|
||||
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
|
||||
const ContentMetaKey key = entry->GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!((meta_type == ContentMetaType::Unknown || key.type == meta_type) && (min < key.id && key.id < max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If application id is present, check if it matches the filter. */
|
||||
if (application_id != InvalidApplicationId) {
|
||||
/* Obtain the content meta for the key. */
|
||||
const void *meta;
|
||||
size_t meta_size;
|
||||
R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key));
|
||||
|
||||
/* Create a reader. */
|
||||
ContentMetaReader reader(meta, meta_size);
|
||||
|
||||
/* Ensure application id matches, if present. */
|
||||
if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the entry to the output buffer. */
|
||||
if (entries_written < out_info.GetSize()) {
|
||||
out_info[entries_written++] = key;
|
||||
}
|
||||
entries_total++;
|
||||
}
|
||||
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
std::optional<ContentMetaKey> found_key = std::nullopt;
|
||||
|
||||
/* Find the last key with the desired program id. */
|
||||
for (auto entry = this->kvs->lower_bound(ContentMetaKey::MakeUnknownType(id, 0)); entry != this->kvs->end(); entry++) {
|
||||
/* No further entries will match the program id, discontinue. */
|
||||
if (entry->GetKey().id != id) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* On memory content database is interested in all keys. */
|
||||
found_key = entry->GetKey();
|
||||
}
|
||||
|
||||
/* Check if the key is absent. */
|
||||
R_UNLESS(found_key, ncm::ResultContentMetaNotFound());
|
||||
|
||||
out_key.SetValue(*found_key);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) {
|
||||
return ResultInvalidContentMetaDatabase();
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseImpl::Commit() {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class OnMemoryContentMetaDatabaseImpl : public ContentMetaDatabaseImpl {
|
||||
public:
|
||||
OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ }
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override;
|
||||
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override;
|
||||
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;
|
||||
virtual Result Commit() override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_placeholder_accessor.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline const char * const BasePlaceHolderDirectory = "/placehld";
|
||||
|
||||
constexpr inline const char * const PlaceHolderExtension = ".nca";
|
||||
constexpr inline size_t PlaceHolderExtensionLength = 4;
|
||||
|
||||
constexpr inline size_t PlaceHolderFileNameLengthWithoutExtension = 2 * sizeof(PlaceHolderId);
|
||||
constexpr inline size_t PlaceHolderFileNameLength = PlaceHolderFileNameLengthWithoutExtension + PlaceHolderExtensionLength;
|
||||
|
||||
void MakeBasePlaceHolderDirectoryPath(PathString *out, const char *root_path) {
|
||||
out->SetFormat("%s%s", root_path, BasePlaceHolderDirectory);
|
||||
}
|
||||
|
||||
void MakePlaceHolderFilePath(PathString *out, PlaceHolderId id, MakePlaceHolderPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeBasePlaceHolderDirectoryPath(std::addressof(path), root_path);
|
||||
func(out, id, path);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Result ConvertNotFoundResult(Result r) {
|
||||
R_TRY_CATCH(r) {
|
||||
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const {
|
||||
MakePlaceHolderFilePath(out_placeholder_path, placeholder_id, this->make_placeholder_path_func, *this->root_path);
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::MakeBaseDirectoryPath(PathString *out, const char *root_path) {
|
||||
MakeBasePlaceHolderDirectoryPath(out, root_path);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id) {
|
||||
PathString path;
|
||||
this->MakePath(std::addressof(path), placeholder_id);
|
||||
return impl::EnsureParentDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name) {
|
||||
/* Ensure placeholder name is valid. */
|
||||
R_UNLESS(strnlen(name, PlaceHolderFileNameLength) == PlaceHolderFileNameLength, ncm::ResultInvalidPlaceHolderFile());
|
||||
R_UNLESS(strncmp(name + PlaceHolderFileNameLengthWithoutExtension, PlaceHolderExtension, PlaceHolderExtensionLength) == 0, ncm::ResultInvalidPlaceHolderFile());
|
||||
|
||||
/* Convert each character pair to a byte until we reach the end. */
|
||||
PlaceHolderId placeholder_id = {};
|
||||
for (size_t i = 0; i < sizeof(placeholder_id); i++) {
|
||||
char tmp[3];
|
||||
strlcpy(tmp, name + i * 2, sizeof(tmp));
|
||||
|
||||
char *err = nullptr;
|
||||
reinterpret_cast<u8 *>(std::addressof(placeholder_id))[i] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16));
|
||||
R_UNLESS(*err == '\x00', ncm::ResultInvalidPlaceHolderFile());
|
||||
}
|
||||
|
||||
*out = placeholder_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) {
|
||||
/* Try to load from the cache. */
|
||||
R_SUCCEED_IF(this->LoadFromCache(out_handle, placeholder_id));
|
||||
|
||||
/* Make the path of the placeholder. */
|
||||
PathString placeholder_path;
|
||||
this->MakePath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Open the placeholder file. */
|
||||
return fs::OpenFile(out_handle, placeholder_path, fs::OpenMode_Write);
|
||||
}
|
||||
|
||||
bool PlaceHolderAccessor::LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) {
|
||||
std::scoped_lock lk(this->cache_mutex);
|
||||
|
||||
/* Attempt to find an entry in the cache. */
|
||||
CacheEntry *entry = this->FindInCache(placeholder_id);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No cached entry found. */
|
||||
entry->id = InvalidPlaceHolderId;
|
||||
*out_handle = entry->handle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle) {
|
||||
std::scoped_lock lk(this->cache_mutex);
|
||||
|
||||
/* Store placeholder id and file handle to a free entry. */
|
||||
CacheEntry *entry = this->GetFreeEntry();
|
||||
entry->id = placeholder_id;
|
||||
entry->handle = handle;
|
||||
entry->counter = this->cur_counter++;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::Invalidate(CacheEntry *entry) {
|
||||
/* Flush and close the cached entry's file. */
|
||||
if (entry != nullptr) {
|
||||
fs::FlushFile(entry->handle);
|
||||
fs::CloseFile(entry->handle);
|
||||
entry->id = InvalidPlaceHolderId;
|
||||
}
|
||||
}
|
||||
|
||||
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) {
|
||||
/* Ensure placeholder id is valid. */
|
||||
if (placeholder_id == InvalidPlaceHolderId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Attempt to find a cache entry with the same placeholder id. */
|
||||
for (size_t i = 0; i < MaxCacheEntries; i++) {
|
||||
if (placeholder_id == this->caches[i].id) {
|
||||
return &this->caches[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() {
|
||||
/* Try to find an already free entry. */
|
||||
for (size_t i = 0; i < MaxCacheEntries; i++) {
|
||||
if (this->caches[i].id == InvalidPlaceHolderId) {
|
||||
return std::addressof(this->caches[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the oldest entry. */
|
||||
CacheEntry *entry = std::addressof(this->caches[0]);
|
||||
for (size_t i = 1; i < MaxCacheEntries; i++) {
|
||||
if (entry->counter < this->caches[i].counter) {
|
||||
entry = std::addressof(this->caches[i]);
|
||||
}
|
||||
}
|
||||
this->Invalidate(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) {
|
||||
{
|
||||
std::scoped_lock lock(this->cache_mutex);
|
||||
this->Invalidate(this->FindInCache(placeholder_id));
|
||||
}
|
||||
this->MakePath(placeholder_path, placeholder_id);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size) {
|
||||
/* Ensure the destination directory exists. */
|
||||
R_TRY(this->EnsurePlaceHolderDirectory(placeholder_id));
|
||||
|
||||
/* Get the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Create the placeholder file. */
|
||||
R_TRY_CATCH(fs::CreateFile(placeholder_path, size, fs::CreateOption_BigFile)) {
|
||||
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::DeletePlaceHolderFile(PlaceHolderId placeholder_id) {
|
||||
/* Get the placeholder path. */
|
||||
PathString placeholder_path;
|
||||
this->GetPath(std::addressof(placeholder_path), placeholder_id);
|
||||
|
||||
/* Delete the placeholder file. */
|
||||
R_TRY_CATCH(fs::DeleteFile(placeholder_path)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size) {
|
||||
/* Open the placeholder file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Store opened files to the cache regardless of write failures. */
|
||||
ON_SCOPE_EXIT { this->StoreToCache(placeholder_id, file); };
|
||||
|
||||
/* Write data to the placeholder file. */
|
||||
return fs::WriteFile(file, offset, buffer, size, this->delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size) {
|
||||
/* Open the placeholder file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Close the file on exit. */
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Set the size of the placeholder file. */
|
||||
return fs::SetFileSize(file, size);
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::TryGetPlaceHolderFileSize(bool *found_in_cache, s64 *out_size, PlaceHolderId placeholder_id) {
|
||||
/* Attempt to find the placeholder in the cache. */
|
||||
fs::FileHandle handle;
|
||||
auto found = this->LoadFromCache(std::addressof(handle), placeholder_id);
|
||||
|
||||
if (found) {
|
||||
/* Renew the entry in the cache. */
|
||||
this->StoreToCache(placeholder_id, handle);
|
||||
R_TRY(fs::GetFileSize(out_size, handle));
|
||||
*found_in_cache = true;
|
||||
} else {
|
||||
*found_in_cache = false;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::InvalidateAll() {
|
||||
/* Invalidate all cache entries. */
|
||||
for (auto &entry : this->caches) {
|
||||
if (entry.id != InvalidPlaceHolderId) {
|
||||
this->Invalidate(std::addressof(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class PlaceHolderAccessor {
|
||||
private:
|
||||
class CacheEntry {
|
||||
public:
|
||||
PlaceHolderId id;
|
||||
fs::FileHandle handle;
|
||||
u64 counter;
|
||||
};
|
||||
|
||||
static constexpr size_t MaxCacheEntries = 0x2;
|
||||
private:
|
||||
std::array<CacheEntry, MaxCacheEntries> caches;
|
||||
PathString *root_path;
|
||||
u64 cur_counter;
|
||||
os::Mutex cache_mutex;
|
||||
MakePlaceHolderPathFunction make_placeholder_path_func;
|
||||
bool delay_flush;
|
||||
private:
|
||||
Result Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id);
|
||||
bool LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id);
|
||||
void StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle);
|
||||
void Invalidate(CacheEntry *entry);
|
||||
CacheEntry *FindInCache(PlaceHolderId placeholder_id);
|
||||
CacheEntry *GetFreeEntry();;
|
||||
public:
|
||||
PlaceHolderAccessor() : cur_counter(0), delay_flush(false) {
|
||||
for (size_t i = 0; i < MaxCacheEntries; i++) {
|
||||
caches[i].id = InvalidPlaceHolderId;
|
||||
}
|
||||
}
|
||||
|
||||
~PlaceHolderAccessor() { this->InvalidateAll(); }
|
||||
|
||||
static void MakeBaseDirectoryPath(PathString *out, const char *root_path);
|
||||
static Result GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name);
|
||||
public:
|
||||
/* API. */
|
||||
void Initialize(PathString *root, MakePlaceHolderPathFunction path_func, bool delay_flush) {
|
||||
this->root_path = root;
|
||||
this->make_placeholder_path_func = path_func;
|
||||
this->delay_flush = delay_flush;
|
||||
}
|
||||
|
||||
Result CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size);
|
||||
Result DeletePlaceHolderFile(PlaceHolderId placeholder_id);
|
||||
Result WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size);
|
||||
Result SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size);
|
||||
Result TryGetPlaceHolderFileSize(bool *out_found, s64 *out_size, PlaceHolderId placeholder_id);
|
||||
|
||||
void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id);
|
||||
void MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const;
|
||||
|
||||
void InvalidateAll();
|
||||
|
||||
Result EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id);
|
||||
size_t GetHierarchicalDirectoryDepth() const { return GetHierarchicalPlaceHolderDirectoryDepth(this->make_placeholder_path_func); }
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_read_only_content_storage_impl.hpp"
|
||||
#include "ncm_fs_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
return func(out, id, root_path);
|
||||
}
|
||||
|
||||
void MakeGameCardContentMetaPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
/* Determine the content path. */
|
||||
PathString path;
|
||||
func(std::addressof(path), id, root_path);
|
||||
|
||||
/* Substitute the .nca extension with .cmnt.nca. */
|
||||
*out = path.GetSubstring(0, path.GetLength() - 4);
|
||||
out->Append(".cnmt.nca");
|
||||
}
|
||||
|
||||
Result OpenContentIdFileImpl(fs::FileHandle *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
|
||||
PathString path;
|
||||
MakeContentPath(std::addressof(path), id, func, root_path);
|
||||
|
||||
/* Open the content file. */
|
||||
/* If absent, make the path for game card content meta and open again. */
|
||||
R_TRY_CATCH(fs::OpenFile(out, path, fs::OpenMode_Read)) {
|
||||
R_CATCH(fs::ResultPathNotFound) {
|
||||
MakeGameCardContentMetaPath(std::addressof(path), id, func, root_path);
|
||||
R_TRY(fs::OpenFile(out, path, fs::OpenMode_Read));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
this->root_path.Set(path);
|
||||
this->make_content_path_func = content_path_func;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::Delete(ContentId content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Make the content path. */
|
||||
PathString content_path;
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Check if the file exists. */
|
||||
bool has;
|
||||
R_TRY(impl::HasFile(std::addressof(has), content_path));
|
||||
|
||||
/* If the file is absent, make the path for game card content meta and check presence again. */
|
||||
if (!has) {
|
||||
MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
R_TRY(impl::HasFile(std::addressof(has), content_path));
|
||||
}
|
||||
|
||||
out.SetValue(has);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Make the path for game card content meta. */
|
||||
PathString content_path;
|
||||
MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
|
||||
/* Check if the file exists. */
|
||||
bool has_file;
|
||||
R_TRY(impl::HasFile(std::addressof(has_file), content_path));
|
||||
|
||||
/* If the file is absent, make the path for regular content. */
|
||||
if (!has_file) {
|
||||
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
|
||||
}
|
||||
|
||||
/* Substitute mount name with the common mount name. */
|
||||
Path common_path;
|
||||
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path));
|
||||
|
||||
out.SetValue(common_path);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::CleanupAllPlaceHolder() {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetContentCount(sf::Out<s32> out_count) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Open the file for the content id. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Determine the file size. */
|
||||
s64 file_size;
|
||||
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
|
||||
|
||||
out_size.SetValue(file_size);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) {
|
||||
/* Ensure offset is valid. */
|
||||
R_UNLESS(offset >= 0, ncm::ResultInvalidOffset());
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Open the file for the content id. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Read from the given offset up to the given size. */
|
||||
R_TRY(fs::ReadFile(file, offset, buf.GetPointer(), buf.GetSize()));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) {
|
||||
/* Obtain the regular rights id for the content id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(this->GetRightsIdFromContentId(&rights_id, content_id));
|
||||
|
||||
/* Output the fs rights id. */
|
||||
out_rights_id.SetValue(rights_id.id);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) {
|
||||
R_TRY(this->EnsureEnabled());
|
||||
|
||||
/* Get the content path. */
|
||||
Path path;
|
||||
R_TRY(this->GetPath(std::addressof(path), content_id));
|
||||
|
||||
/* Get the rights id. */
|
||||
ncm::RightsId rights_id;
|
||||
R_TRY(GetRightsId(&rights_id, path));
|
||||
out_rights_id.SetValue(rights_id);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) {
|
||||
out_size.SetValue(0);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) {
|
||||
out_size.SetValue(0);
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::FlushPlaceHolder() {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::RepairInvalidFileAttribute() {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) {
|
||||
return ncm::ResultWriteToReadOnlyContentStorage();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_content_storage_impl_base.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class ReadOnlyContentStorageImpl : public ContentStorageImplBase {
|
||||
public:
|
||||
Result Initialize(const char *root_path, MakeContentPathFunction content_path_func);
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
|
||||
virtual Result Delete(ContentId content_id) override;
|
||||
virtual Result Has(sf::Out<bool> out, ContentId content_id) override;
|
||||
virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override;
|
||||
virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result CleanupAllPlaceHolder() override;
|
||||
virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override;
|
||||
virtual Result GetContentCount(sf::Out<s32> out_count) override;
|
||||
virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) override;
|
||||
virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override;
|
||||
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) override;
|
||||
virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override;
|
||||
virtual Result FlushPlaceHolder() override;
|
||||
virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result RepairInvalidFileAttribute() override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "ncm_remote_content_storage_impl.hpp"
|
||||
#include "ncm_remote_content_meta_database_impl.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class RemoteContentManagerImpl final : public IContentManager {
|
||||
public:
|
||||
RemoteContentManagerImpl() { /* ... */ }
|
||||
|
||||
~RemoteContentManagerImpl() { /* ... */ }
|
||||
public:
|
||||
virtual Result CreateContentStorage(StorageId storage_id) override {
|
||||
return ::ncmCreateContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result CreateContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmCreateContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result VerifyContentStorage(StorageId storage_id) override {
|
||||
return ::ncmVerifyContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result VerifyContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmVerifyContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result OpenContentStorage(sf::Out<std::shared_ptr<IContentStorage>> out, StorageId storage_id) override {
|
||||
NcmContentStorage cs;
|
||||
R_TRY(::ncmOpenContentStorage(std::addressof(cs), static_cast<NcmStorageId>(storage_id)));
|
||||
|
||||
out.SetValue(std::make_shared<RemoteContentStorageImpl>(cs));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) override {
|
||||
NcmContentMetaDatabase db;
|
||||
R_TRY(::ncmOpenContentMetaDatabase(std::addressof(db), static_cast<NcmStorageId>(storage_id)));
|
||||
|
||||
out.SetValue(std::make_shared<RemoteContentMetaDatabaseImpl>(db));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result CloseContentStorageForcibly(StorageId storage_id) override {
|
||||
return ::ncmCloseContentStorageForcibly(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result CloseContentMetaDatabaseForcibly(StorageId storage_id) override {
|
||||
return ::ncmCloseContentMetaDatabaseForcibly(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result CleanupContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmCleanupContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result ActivateContentStorage(StorageId storage_id) override {
|
||||
return ::ncmActivateContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result InactivateContentStorage(StorageId storage_id) override {
|
||||
return ::ncmInactivateContentStorage(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result ActivateContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmActivateContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result InactivateContentMetaDatabase(StorageId storage_id) override {
|
||||
return ::ncmInactivateContentMetaDatabase(static_cast<NcmStorageId>(storage_id));
|
||||
}
|
||||
|
||||
virtual Result InvalidateRightsIdCache() override {
|
||||
return ::ncmInvalidateRightsIdCache();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class RemoteContentMetaDatabaseImpl final : public IContentMetaDatabase {
|
||||
private:
|
||||
::NcmContentMetaDatabase srv;
|
||||
public:
|
||||
RemoteContentMetaDatabaseImpl(::NcmContentMetaDatabase &db) : srv(db) { /* ... */ }
|
||||
|
||||
~RemoteContentMetaDatabaseImpl() { ::ncmContentMetaDatabaseClose(std::addressof(srv)); }
|
||||
private:
|
||||
ALWAYS_INLINE ::NcmContentMetaKey *Convert(ContentMetaKey *k) {
|
||||
static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey));
|
||||
return reinterpret_cast<::NcmContentMetaKey *>(k);
|
||||
}
|
||||
ALWAYS_INLINE const ::NcmContentMetaKey *Convert(const ContentMetaKey *k) {
|
||||
static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey));
|
||||
return reinterpret_cast<const ::NcmContentMetaKey *>(k);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE const ::NcmContentMetaKey *Convert(const ContentMetaKey &k) {
|
||||
static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey));
|
||||
return reinterpret_cast<const ::NcmContentMetaKey *>(std::addressof(k));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmApplicationContentMetaKey *Convert(ApplicationContentMetaKey *k) {
|
||||
static_assert(sizeof(ApplicationContentMetaKey) == sizeof(::NcmApplicationContentMetaKey));
|
||||
return reinterpret_cast<::NcmApplicationContentMetaKey *>(k);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentInfo *Convert(ContentInfo *c) {
|
||||
static_assert(sizeof(ContentInfo) == sizeof(::NcmContentInfo));
|
||||
return reinterpret_cast<::NcmContentInfo *>(c);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId *c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(c);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId &c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(std::addressof(c));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId *c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<const ::NcmContentId *>(c);
|
||||
}
|
||||
|
||||
|
||||
ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId &c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<const ::NcmContentId *>(std::addressof(c));
|
||||
}
|
||||
public:
|
||||
virtual Result Set(const ContentMetaKey &key, sf::InBuffer value) override {
|
||||
return ncmContentMetaDatabaseSet(std::addressof(this->srv), Convert(key), value.GetPointer(), value.GetSize());
|
||||
}
|
||||
|
||||
virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, sf::OutBuffer out_value) override {
|
||||
return ncmContentMetaDatabaseGet(std::addressof(this->srv), Convert(key), out_size.GetPointer(), out_value.GetPointer(), out_value.GetSize());
|
||||
}
|
||||
|
||||
virtual Result Remove(const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseRemove(std::addressof(this->srv), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) override {
|
||||
return ncmContentMetaDatabaseGetContentIdByType(std::addressof(this->srv), Convert(out_content_id.GetPointer()), Convert(key), static_cast<::NcmContentType>(type));
|
||||
}
|
||||
|
||||
virtual Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) override {
|
||||
return ncmContentMetaDatabaseListContentInfo(std::addressof(this->srv), out_entries_written.GetPointer(), Convert(out_info.GetPointer()), out_info.GetSize(), Convert(key), offset);
|
||||
}
|
||||
|
||||
virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override {
|
||||
return ncmContentMetaDatabaseList(std::addressof(this->srv), out_entries_total.GetPointer(), out_entries_written.GetPointer(), Convert(out_info.GetPointer()), out_info.GetSize(), static_cast<::NcmContentMetaType>(meta_type), application_id.value, min, max, static_cast<::NcmContentInstallType>(install_type));
|
||||
}
|
||||
|
||||
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override {
|
||||
return ncmContentMetaDatabaseGetLatestContentMetaKey(std::addressof(this->srv), Convert(out_key.GetPointer()), static_cast<u64>(id));
|
||||
}
|
||||
|
||||
virtual Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) override {
|
||||
return ncmContentMetaDatabaseListApplication(std::addressof(this->srv), out_entries_total.GetPointer(), out_entries_written.GetPointer(), Convert(out_keys.GetPointer()), out_keys.GetSize(), static_cast<::NcmContentMetaType>(meta_type));
|
||||
}
|
||||
|
||||
virtual Result Has(sf::Out<bool> out, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseHas(std::addressof(this->srv), out.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) override {
|
||||
return ncmContentMetaDatabaseHasAll(std::addressof(this->srv), out.GetPointer(), Convert(keys.GetPointer()), keys.GetSize());
|
||||
}
|
||||
|
||||
virtual Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetSize(std::addressof(this->srv), out_size.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetRequiredSystemVersion(std::addressof(this->srv), out_version.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetPatchId(sf::Out<PatchId> out_patch_id, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetPatchId(std::addressof(this->srv), reinterpret_cast<u64 *>(out_patch_id.GetPointer()), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result DisableForcibly() override {
|
||||
return ncmContentMetaDatabaseDisableForcibly(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override {
|
||||
return ncmContentMetaDatabaseLookupOrphanContent(std::addressof(this->srv), out_orphaned.GetPointer(), Convert(content_ids.GetPointer()), std::min(out_orphaned.GetSize(), content_ids.GetSize()));
|
||||
}
|
||||
|
||||
virtual Result Commit() override {
|
||||
return ncmContentMetaDatabaseCommit(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) override {
|
||||
return ncmContentMetaDatabaseHasContent(std::addressof(this->srv), out.GetPointer(), Convert(key), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) override {
|
||||
return ncmContentMetaDatabaseListContentMetaInfo(std::addressof(this->srv), out_entries_written.GetPointer(), out_meta_info.GetPointer(), out_meta_info.GetSize(), Convert(key), offset);
|
||||
}
|
||||
|
||||
virtual Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) override {
|
||||
static_assert(sizeof(ContentMetaAttribute) == sizeof(u8));
|
||||
return ncmContentMetaDatabaseGetAttributes(std::addressof(this->srv), Convert(key), out_attributes.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override {
|
||||
return ncmContentMetaDatabaseGetRequiredApplicationVersion(std::addressof(this->srv), out_version.GetPointer(), Convert(key));
|
||||
}
|
||||
|
||||
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) override {
|
||||
return ncmContentMetaDatabaseGetContentIdByTypeAndIdOffset(std::addressof(this->srv), Convert(out_content_id.GetPointer()), Convert(key), static_cast<::NcmContentType>(type), id_offset);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 Adubbz, Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
class RemoteContentStorageImpl final : public IContentStorage {
|
||||
private:
|
||||
::NcmContentStorage srv;
|
||||
public:
|
||||
RemoteContentStorageImpl(::NcmContentStorage &cs) : srv(cs) { /* ... */ }
|
||||
|
||||
~RemoteContentStorageImpl() { ::ncmContentStorageClose(std::addressof(srv)); }
|
||||
private:
|
||||
ALWAYS_INLINE ::NcmPlaceHolderId *Convert(PlaceHolderId *p) {
|
||||
static_assert(sizeof(PlaceHolderId) == sizeof(::NcmPlaceHolderId));
|
||||
return reinterpret_cast<::NcmPlaceHolderId *>(p);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmPlaceHolderId *Convert(PlaceHolderId &p) {
|
||||
static_assert(sizeof(PlaceHolderId) == sizeof(::NcmPlaceHolderId));
|
||||
return reinterpret_cast<::NcmPlaceHolderId *>(std::addressof(p));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId *c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(c);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ::NcmContentId *Convert(ContentId &c) {
|
||||
static_assert(sizeof(ContentId) == sizeof(::NcmContentId));
|
||||
return reinterpret_cast<::NcmContentId *>(std::addressof(c));
|
||||
}
|
||||
public:
|
||||
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override {
|
||||
return ncmContentStorageGeneratePlaceHolderId(std::addressof(this->srv), Convert(out.GetPointer()));
|
||||
}
|
||||
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) override {
|
||||
static_assert(alignof(ContentId) < alignof(PlaceHolderId));
|
||||
return ncmContentStorageCreatePlaceHolder(std::addressof(this->srv), Convert(content_id), Convert(placeholder_id), size);
|
||||
}
|
||||
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageDeletePlaceHolder(std::addressof(this->srv), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageHasPlaceHolder(std::addressof(this->srv), out.GetPointer(), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, sf::InBuffer data) override {
|
||||
return ncmContentStorageWritePlaceHolder(std::addressof(this->srv), Convert(placeholder_id), offset, data.GetPointer(), data.GetSize());
|
||||
}
|
||||
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override {
|
||||
static_assert(alignof(ContentId) < alignof(PlaceHolderId));
|
||||
return ncmContentStorageRegister(std::addressof(this->srv), Convert(content_id), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result Delete(ContentId content_id) override {
|
||||
return ncmContentStorageDelete(std::addressof(this->srv), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result Has(sf::Out<bool> out, ContentId content_id) override {
|
||||
return ncmContentStorageHas(std::addressof(this->srv), out.GetPointer(), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override {
|
||||
return ncmContentStorageGetPath(std::addressof(this->srv), out.GetPointer()->str, sizeof(out.GetPointer()->str), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageGetPlaceHolderPath(std::addressof(this->srv), out.GetPointer()->str, sizeof(out.GetPointer()->str), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result CleanupAllPlaceHolder() override {
|
||||
return ncmContentStorageCleanupAllPlaceHolder(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override {
|
||||
return ncmContentStorageListPlaceHolder(std::addressof(this->srv), Convert(out_buf.GetPointer()), out_buf.GetSize(), out_count.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result GetContentCount(sf::Out<s32> out_count) override {
|
||||
return ncmContentStorageGetContentCount(std::addressof(this->srv), out_count.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) override {
|
||||
return ncmContentStorageListContentId(std::addressof(this->srv), Convert(out_buf.GetPointer()), out_buf.GetSize(), out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override {
|
||||
return ncmContentStorageGetSizeFromContentId(std::addressof(this->srv), out_size.GetPointer(), Convert(content_id));
|
||||
}
|
||||
|
||||
virtual Result DisableForcibly() override {
|
||||
return ncmContentStorageDisableForcibly(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override {
|
||||
return ncmContentStorageRevertToPlaceHolder(std::addressof(this->srv), Convert(placeholder_id), Convert(old_content_id), Convert(new_content_id));
|
||||
}
|
||||
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override {
|
||||
return ncmContentStorageSetPlaceHolderSize(std::addressof(this->srv), Convert(placeholder_id), size);
|
||||
}
|
||||
|
||||
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, s64 offset) override {
|
||||
return ncmContentStorageReadContentIdFile(std::addressof(this->srv), buf.GetPointer(), buf.GetSize(), Convert(content_id), offset);
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromPlaceHolderId(std::addressof(this->srv), std::addressof(rights_id), Convert(placeholder_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromPlaceHolderId(std::addressof(this->srv), std::addressof(rights_id), Convert(placeholder_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(this->srv), std::addressof(rights_id), Convert(content_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override {
|
||||
::NcmRightsId rights_id;
|
||||
R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(this->srv), std::addressof(rights_id), Convert(content_id)));
|
||||
|
||||
static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id));
|
||||
std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
virtual Result WriteContentForDebug(ContentId content_id, s64 offset, sf::InBuffer data) override {
|
||||
return ncmContentStorageWriteContentForDebug(std::addressof(this->srv), Convert(content_id), offset, data.GetPointer(), data.GetSize());
|
||||
}
|
||||
|
||||
virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override {
|
||||
return ncmContentStorageGetFreeSpaceSize(std::addressof(this->srv), out_size.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override {
|
||||
return ncmContentStorageGetTotalSpaceSize(std::addressof(this->srv), out_size.GetPointer());
|
||||
}
|
||||
|
||||
virtual Result FlushPlaceHolder() override {
|
||||
return ncmContentStorageFlushPlaceHolder(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out_size, PlaceHolderId placeholder_id) override {
|
||||
return ncmContentStorageGetSizeFromPlaceHolderId(std::addressof(this->srv), out_size.GetPointer(), Convert(placeholder_id));
|
||||
}
|
||||
|
||||
virtual Result RepairInvalidFileAttribute() override {
|
||||
return ncmContentStorageRepairInvalidFileAttribute(std::addressof(this->srv));
|
||||
}
|
||||
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) override {
|
||||
static_assert(sizeof(::NcmRightsId) == sizeof(ncm::RightsId));
|
||||
::NcmRightsId *out = reinterpret_cast<::NcmRightsId *>(out_rights_id.GetPointer());
|
||||
return ncmContentStorageGetRightsIdFromPlaceHolderIdWithCache(std::addressof(this->srv), out, Convert(placeholder_id), Convert(cache_content_id));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -32,25 +32,26 @@ namespace ams::updater {
|
|||
/* Configuration Prototypes. */
|
||||
bool HasEks(BootImageUpdateType boot_image_update_type);
|
||||
bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type);
|
||||
NcmContentMetaType GetNcmContentMetaType(BootModeType mode);
|
||||
Result GetBootImagePackageDataId(u64 *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size);
|
||||
ncm::ContentMetaType GetContentMetaType(BootModeType mode);
|
||||
Result GetBootImagePackageId(ncm::SystemDataId *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size);
|
||||
|
||||
/* Verification Prototypes. */
|
||||
Result GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size);
|
||||
Result VerifyBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImages(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result VerifyBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
|
||||
/* Update Prototypes. */
|
||||
Result SetVerificationNeeded(BootModeType mode, bool needed, void *work_buffer, size_t work_buffer_size);
|
||||
Result UpdateBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImages(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result UpdateBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
|
||||
/* Package helpers. */
|
||||
Result ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type);
|
||||
Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which);
|
||||
Result WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type);
|
||||
Result CompareHash(const void *lhs, const void *rhs, size_t size);
|
||||
|
||||
/* Implementations. */
|
||||
Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size) {
|
||||
|
@ -80,12 +81,12 @@ namespace ams::updater {
|
|||
}
|
||||
}
|
||||
|
||||
NcmContentMetaType GetNcmContentMetaType(BootModeType mode) {
|
||||
ncm::ContentMetaType GetContentMetaType(BootModeType mode) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return NcmContentMetaType_BootImagePackage;
|
||||
return ncm::ContentMetaType::BootImagePackage;
|
||||
case BootModeType::Safe:
|
||||
return NcmContentMetaType_BootImagePackageSafe;
|
||||
return ncm::ContentMetaType::BootImagePackageSafe;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
@ -114,8 +115,8 @@ namespace ams::updater {
|
|||
|
||||
Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Get system data id for boot images (819/81A/81B/81C). */
|
||||
u64 bip_data_id = 0;
|
||||
R_TRY(GetBootImagePackageDataId(&bip_data_id, mode, work_buffer, work_buffer_size));
|
||||
ncm::SystemDataId bip_data_id;
|
||||
R_TRY(GetBootImagePackageId(&bip_data_id, mode, work_buffer, work_buffer_size));
|
||||
|
||||
/* Verify the boot images in NAND. */
|
||||
R_TRY_CATCH(VerifyBootImages(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type)) {
|
||||
|
@ -130,47 +131,40 @@ namespace ams::updater {
|
|||
return SetVerificationNeeded(mode, false, work_buffer, work_buffer_size);
|
||||
}
|
||||
|
||||
Result GetBootImagePackageDataId(u64 *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size) {
|
||||
Result GetBootImagePackageId(ncm::SystemDataId *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size) {
|
||||
/* Ensure we can read content metas. */
|
||||
constexpr size_t MaxContentMetas = 0x40;
|
||||
AMS_ABORT_UNLESS(work_buffer_size >= sizeof(NcmContentMetaKey) * MaxContentMetas);
|
||||
AMS_ABORT_UNLESS(work_buffer_size >= sizeof(ncm::ContentMetaKey) * MaxContentMetas);
|
||||
|
||||
/* Open NAND System meta database, list contents. */
|
||||
NcmContentMetaDatabase meta_db;
|
||||
R_TRY(ncmOpenContentMetaDatabase(&meta_db, NcmStorageId_BuiltInSystem));
|
||||
ON_SCOPE_EXIT { serviceClose(&meta_db.s); };
|
||||
ncm::ContentMetaDatabase db;
|
||||
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(db), ncm::StorageId::BuiltInSystem));
|
||||
|
||||
NcmContentMetaKey *records = reinterpret_cast<NcmContentMetaKey *>(work_buffer);
|
||||
ncm::ContentMetaKey *keys = reinterpret_cast<ncm::ContentMetaKey *>(work_buffer);
|
||||
const auto content_meta_type = GetContentMetaType(mode);
|
||||
|
||||
const auto content_meta_type = GetNcmContentMetaType(mode);
|
||||
s32 written_entries;
|
||||
s32 total_entries;
|
||||
R_TRY(ncmContentMetaDatabaseList(&meta_db, &total_entries, &written_entries, records, MaxContentMetas * sizeof(*records), content_meta_type, 0, 0, UINT64_MAX, NcmContentInstallType_Full));
|
||||
if (total_entries <= 0) {
|
||||
return ResultBootImagePackageNotFound();
|
||||
}
|
||||
|
||||
AMS_ABORT_UNLESS(total_entries == written_entries);
|
||||
auto count = db.ListContentMeta(keys, MaxContentMetas, content_meta_type);
|
||||
R_UNLESS(count.total > 0, ResultBootImagePackageNotFound());
|
||||
|
||||
/* Output is sorted, return the lowest valid exfat entry. */
|
||||
if (total_entries > 1) {
|
||||
for (size_t i = 0; i < size_t(total_entries); i++) {
|
||||
if (count.total > 1) {
|
||||
for (auto i = 0; i < count.total; i++) {
|
||||
u8 attr;
|
||||
R_TRY(ncmContentMetaDatabaseGetAttributes(&meta_db, &records[i], &attr));
|
||||
R_TRY(db.GetAttributes(std::addressof(attr), keys[i]));
|
||||
|
||||
if (attr & NcmContentMetaAttribute_IncludesExFatDriver) {
|
||||
*out_data_id = records[i].id;
|
||||
if (attr & ncm::ContentMetaAttribute_IncludesExFatDriver) {
|
||||
out_data_id->value = keys[i].id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If there's only one entry or no exfat entries, return that entry. */
|
||||
*out_data_id = records[0].id;
|
||||
out_data_id->value = keys[0].id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result VerifyBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
Result VerifyBootImages(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return VerifyBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type);
|
||||
|
@ -180,20 +174,22 @@ namespace ams::updater {
|
|||
}
|
||||
}
|
||||
|
||||
Result VerifyBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
Result VerifyBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
|
||||
/* Mount the boot image package. */
|
||||
const char *mount_name = GetMountName();
|
||||
R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) {
|
||||
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { R_ABORT_UNLESS(romfsUnmount(GetBootImagePackageMountPath())); };
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name); };
|
||||
|
||||
/* Read and validate hashes of boot images. */
|
||||
{
|
||||
size_t size;
|
||||
u8 nand_hash[SHA256_HASH_SIZE];
|
||||
u8 file_hash[SHA256_HASH_SIZE];
|
||||
u8 nand_hash[crypto::Sha256Generator::HashSize];
|
||||
u8 file_hash[crypto::Sha256Generator::HashSize];
|
||||
|
||||
Boot0Accessor boot0_accessor;
|
||||
R_TRY(boot0_accessor.Initialize());
|
||||
|
@ -209,44 +205,42 @@ namespace ams::updater {
|
|||
|
||||
/* Compare Package1 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
|
||||
R_TRY(boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
|
||||
/* Compare Package2 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result VerifyBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
Result VerifyBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
|
||||
/* Mount the boot image package. */
|
||||
const char *mount_name = GetMountName();
|
||||
R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) {
|
||||
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { R_ABORT_UNLESS(romfsUnmount(GetBootImagePackageMountPath())); };
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name); };
|
||||
|
||||
/* Read and validate hashes of boot images. */
|
||||
{
|
||||
size_t size;
|
||||
u8 nand_hash[SHA256_HASH_SIZE];
|
||||
u8 file_hash[SHA256_HASH_SIZE];
|
||||
u8 nand_hash[crypto::Sha256Generator::HashSize];
|
||||
u8 file_hash[crypto::Sha256Generator::HashSize];
|
||||
|
||||
Boot0Accessor boot0_accessor;
|
||||
R_TRY(boot0_accessor.Initialize());
|
||||
|
@ -267,31 +261,27 @@ namespace ams::updater {
|
|||
|
||||
/* Compare Package1 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY(boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
|
||||
R_TRY(boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
|
||||
/* Compare Package2 Normal/Sub hashes. */
|
||||
R_TRY(GetFileHash(&size, file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeMain));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
|
||||
R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeSub));
|
||||
if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash)));
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result UpdateBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
Result UpdateBootImages(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
switch (mode) {
|
||||
case BootModeType::Normal:
|
||||
return UpdateBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type);
|
||||
|
@ -301,14 +291,16 @@ namespace ams::updater {
|
|||
}
|
||||
}
|
||||
|
||||
Result UpdateBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
Result UpdateBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
|
||||
/* Mount the boot image package. */
|
||||
const char *mount_name = GetMountName();
|
||||
R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) {
|
||||
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { R_ABORT_UNLESS(romfsUnmount(GetBootImagePackageMountPath())); };
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name); };
|
||||
|
||||
{
|
||||
Boot0Accessor boot0_accessor;
|
||||
|
@ -356,14 +348,16 @@ namespace ams::updater {
|
|||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result UpdateBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
Result UpdateBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) {
|
||||
/* Ensure work buffer is big enough for us to do what we want to do. */
|
||||
R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size));
|
||||
|
||||
R_TRY_CATCH(romfsMountFromDataArchive(data_id, NcmStorageId_BuiltInSystem, GetBootImagePackageMountPath())) {
|
||||
/* Mount the boot image package. */
|
||||
const char *mount_name = GetMountName();
|
||||
R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) {
|
||||
R_CONVERT(fs::ResultTargetNotFound, ResultBootImagePackageNotFound())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { R_ABORT_UNLESS(romfsUnmount(GetBootImagePackageMountPath())); };
|
||||
ON_SCOPE_EXIT { fs::Unmount(mount_name); };
|
||||
|
||||
{
|
||||
Boot0Accessor boot0_accessor;
|
||||
|
@ -450,14 +444,10 @@ namespace ams::updater {
|
|||
R_TRY(accessor.PreserveAutoRcm(bct, work, which));
|
||||
}
|
||||
|
||||
u8 file_hash[SHA256_HASH_SIZE];
|
||||
sha256CalculateHash(file_hash, bct, BctSize);
|
||||
u8 file_hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256Hash(file_hash, sizeof(file_hash), bct, BctSize);
|
||||
|
||||
if (std::memcmp(file_hash, stored_hash, SHA256_HASH_SIZE) != 0) {
|
||||
return ResultNeedsRepairBootImages();
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
return CompareHash(file_hash, stored_hash, sizeof(file_hash));
|
||||
}
|
||||
|
||||
Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which) {
|
||||
|
@ -476,6 +466,11 @@ namespace ams::updater {
|
|||
return accessor.Write(GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size, Package2Partition::Package2);
|
||||
}
|
||||
|
||||
Result CompareHash(const void *lhs, const void *rhs, size_t size) {
|
||||
R_UNLESS(crypto::IsSameBytes(lhs, rhs, size), ResultNeedsRepairBootImages());
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BootImageUpdateType GetBootImageUpdateType(spl::HardwareType hw_type) {
|
||||
|
@ -508,7 +503,7 @@ namespace ams::updater {
|
|||
}
|
||||
|
||||
/* Get a session to ncm. */
|
||||
sm::ScopedServiceHolder<ncmInitialize, ncmExit> ncm_holder;
|
||||
sm::ScopedServiceHolder<ncm::Initialize, ncm::Finalize> ncm_holder;
|
||||
R_ABORT_UNLESS(ncm_holder.GetResult());
|
||||
|
||||
/* Verify normal, verify safe as needed. */
|
||||
|
|
|
@ -18,47 +18,38 @@
|
|||
namespace ams::updater {
|
||||
|
||||
Result BisAccessor::Initialize() {
|
||||
R_TRY(fsOpenBisStorage(&this->storage, this->partition_id));
|
||||
this->active = true;
|
||||
return ResultSuccess();
|
||||
return fs::OpenBisPartition(std::addressof(this->storage), this->partition_id);
|
||||
}
|
||||
|
||||
void BisAccessor::Finalize() {
|
||||
if (this->active) {
|
||||
fsStorageClose(&this->storage);
|
||||
this->active = false;
|
||||
}
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Result BisAccessor::Read(void *dst, size_t size, u64 offset) {
|
||||
AMS_ABORT_UNLESS((offset % SectorAlignment) == 0);
|
||||
return fsStorageRead(&this->storage, offset, dst, size);
|
||||
return this->storage->Read(static_cast<u32>(offset), dst, size);
|
||||
}
|
||||
|
||||
Result BisAccessor::Write(u64 offset, const void *src, size_t size) {
|
||||
AMS_ABORT_UNLESS((offset % SectorAlignment) == 0);
|
||||
return fsStorageWrite(&this->storage, offset, src, size);
|
||||
return this->storage->Write(static_cast<u32>(offset), src, size);
|
||||
}
|
||||
|
||||
Result BisAccessor::Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size) {
|
||||
AMS_ABORT_UNLESS((offset % SectorAlignment) == 0);
|
||||
AMS_ABORT_UNLESS((work_buffer_size % SectorAlignment) == 0);
|
||||
|
||||
FILE *bip_fp = fopen(bip_path, "rb");
|
||||
if (bip_fp == NULL) {
|
||||
return ResultInvalidBootImagePackage();
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(bip_fp); };
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(fs::OpenFile(std::addressof(file), bip_path, fs::OpenMode_Read)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultInvalidBootImagePackage())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
size_t written = 0;
|
||||
while (true) {
|
||||
std::memset(work_buffer, 0, work_buffer_size);
|
||||
size_t read_size = fread(work_buffer, 1, work_buffer_size, bip_fp);
|
||||
if (read_size != work_buffer_size) {
|
||||
if (ferror(bip_fp)) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
}
|
||||
size_t read_size;
|
||||
R_TRY(fs::ReadFile(std::addressof(read_size), file, written, work_buffer, work_buffer_size, fs::ReadOption()));
|
||||
AMS_ABORT_UNLESS(written + read_size <= size);
|
||||
|
||||
size_t aligned_size = ((read_size + SectorAlignment - 1) / SectorAlignment) * SectorAlignment;
|
||||
|
@ -91,18 +82,19 @@ namespace ams::updater {
|
|||
AMS_ABORT_UNLESS((offset % SectorAlignment) == 0);
|
||||
AMS_ABORT_UNLESS((work_buffer_size % SectorAlignment) == 0);
|
||||
|
||||
Sha256Context sha_ctx;
|
||||
sha256ContextCreate(&sha_ctx);
|
||||
crypto::Sha256Generator generator;
|
||||
generator.Initialize();
|
||||
|
||||
size_t total_read = 0;
|
||||
while (total_read < hash_size) {
|
||||
size_t cur_read_size = std::min(work_buffer_size, size - total_read);
|
||||
size_t cur_update_size = std::min(cur_read_size, hash_size - total_read);
|
||||
R_TRY(this->Read(work_buffer, cur_read_size, offset + total_read));
|
||||
sha256ContextUpdate(&sha_ctx, work_buffer, cur_update_size);
|
||||
generator.Update(work_buffer, cur_update_size);
|
||||
|
||||
total_read += cur_read_size;
|
||||
}
|
||||
sha256ContextGetHash(&sha_ctx, dst);
|
||||
generator.GetHash(dst, hash_size);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
@ -140,9 +132,10 @@ namespace ams::updater {
|
|||
size_t read_size;
|
||||
R_TRY(this->Read(&read_size, work_buffer, BctSize, which));
|
||||
|
||||
void *dst_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(dst_bct) + BctPubkOffset);
|
||||
void *dst_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(dst_bct) + BctPubkOffset);
|
||||
void *src_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctPubkOffset);
|
||||
std::memcpy(dst_pubk, src_pubk, BctPubkSize);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,19 +20,14 @@
|
|||
namespace ams::updater {
|
||||
|
||||
class BisAccessor {
|
||||
NON_COPYABLE(BisAccessor);
|
||||
public:
|
||||
static constexpr size_t SectorAlignment = 0x200;
|
||||
private:
|
||||
FsStorage storage = {};
|
||||
FsBisPartitionId partition_id;
|
||||
bool active;
|
||||
std::unique_ptr<fs::IStorage> storage;
|
||||
const fs::BisPartitionId partition_id;
|
||||
public:
|
||||
BisAccessor(FsBisPartitionId id) : partition_id(id), active(false) { }
|
||||
~BisAccessor() {
|
||||
if (this->active) {
|
||||
fsStorageClose(&storage);
|
||||
}
|
||||
}
|
||||
explicit BisAccessor(fs::BisPartitionId id) : partition_id(id) { /* ... */ }
|
||||
|
||||
public:
|
||||
Result Initialize();
|
||||
|
@ -79,7 +74,7 @@ namespace ams::updater {
|
|||
};
|
||||
|
||||
struct Boot0Meta {
|
||||
using EnumType = Boot0Partition;
|
||||
using EnumType = Boot0Partition;
|
||||
using OffsetSizeType = OffsetSizeEntry<EnumType>;
|
||||
|
||||
static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count);
|
||||
|
@ -96,7 +91,7 @@ namespace ams::updater {
|
|||
};
|
||||
|
||||
struct Boot1Meta {
|
||||
using EnumType = Boot1Partition;
|
||||
using EnumType = Boot1Partition;
|
||||
using OffsetSizeType = OffsetSizeEntry<EnumType>;
|
||||
|
||||
static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count);
|
||||
|
@ -109,7 +104,7 @@ namespace ams::updater {
|
|||
};
|
||||
|
||||
struct Package2Meta {
|
||||
using EnumType = Package2Partition;
|
||||
using EnumType = Package2Partition;
|
||||
using OffsetSizeType = OffsetSizeEntry<EnumType>;
|
||||
|
||||
static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count);
|
||||
|
@ -121,11 +116,12 @@ namespace ams::updater {
|
|||
|
||||
template<typename Meta>
|
||||
class PartitionAccessor : public BisAccessor {
|
||||
NON_COPYABLE(PartitionAccessor);
|
||||
public:
|
||||
using EnumType = typename Meta::EnumType;
|
||||
using EnumType = typename Meta::EnumType;
|
||||
using OffsetSizeType = typename Meta::OffsetSizeType;
|
||||
public:
|
||||
PartitionAccessor(FsBisPartitionId id) : BisAccessor(id) { }
|
||||
explicit PartitionAccessor(fs::BisPartitionId id) : BisAccessor(id) { /* ... */ }
|
||||
private:
|
||||
constexpr const OffsetSizeType *FindEntry(EnumType which) {
|
||||
const OffsetSizeType *entry = nullptr;
|
||||
|
@ -182,27 +178,27 @@ namespace ams::updater {
|
|||
RepairSub,
|
||||
};
|
||||
|
||||
static constexpr FsBisPartitionId GetPackage2StorageId(Package2Type which) {
|
||||
static constexpr fs::BisPartitionId GetPackage2StorageId(Package2Type which) {
|
||||
switch (which) {
|
||||
case Package2Type::NormalMain:
|
||||
return FsBisPartitionId_BootConfigAndPackage2Part1;
|
||||
return fs::BisPartitionId::BootConfigAndPackage2Part1;
|
||||
case Package2Type::NormalSub:
|
||||
return FsBisPartitionId_BootConfigAndPackage2Part2;
|
||||
return fs::BisPartitionId::BootConfigAndPackage2Part2;
|
||||
case Package2Type::SafeMain:
|
||||
return FsBisPartitionId_BootConfigAndPackage2Part3;
|
||||
return fs::BisPartitionId::BootConfigAndPackage2Part3;
|
||||
case Package2Type::SafeSub:
|
||||
return FsBisPartitionId_BootConfigAndPackage2Part4;
|
||||
return fs::BisPartitionId::BootConfigAndPackage2Part4;
|
||||
case Package2Type::RepairMain:
|
||||
return FsBisPartitionId_BootConfigAndPackage2Part5;
|
||||
return fs::BisPartitionId::BootConfigAndPackage2Part5;
|
||||
case Package2Type::RepairSub:
|
||||
return FsBisPartitionId_BootConfigAndPackage2Part6;
|
||||
return fs::BisPartitionId::BootConfigAndPackage2Part6;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
class Boot0Accessor : public PartitionAccessor<Boot0Meta> {
|
||||
public:
|
||||
static constexpr FsBisPartitionId PartitionId = FsBisPartitionId_BootPartition1Root;
|
||||
static constexpr fs::BisPartitionId PartitionId = fs::BisPartitionId::BootPartition1Root;
|
||||
static constexpr size_t BctPubkOffset = 0x210;
|
||||
static constexpr size_t BctPubkSize = 0x100;
|
||||
static constexpr size_t BctEksOffset = 0x450;
|
||||
|
@ -222,7 +218,7 @@ namespace ams::updater {
|
|||
|
||||
class Boot1Accessor : public PartitionAccessor<Boot1Meta> {
|
||||
public:
|
||||
static constexpr FsBisPartitionId PartitionId = FsBisPartitionId_BootPartition2Root;
|
||||
static constexpr fs::BisPartitionId PartitionId = fs::BisPartitionId::BootPartition2Root;
|
||||
public:
|
||||
Boot1Accessor() : PartitionAccessor<Boot1Meta>(PartitionId) { }
|
||||
};
|
||||
|
|
|
@ -18,49 +18,43 @@
|
|||
namespace ams::updater {
|
||||
|
||||
Result ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
return ResultInvalidBootImagePackage();
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
/* Open the file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultInvalidBootImagePackage())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
std::memset(dst, 0, dst_size);
|
||||
size_t read_size = fread(dst, 1, dst_size, fp);
|
||||
if (ferror(fp)) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
*out_size = read_size;
|
||||
return ResultSuccess();
|
||||
return fs::ReadFile(out_size, file, 0, dst, dst_size, fs::ReadOption());
|
||||
}
|
||||
|
||||
Result GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
return ResultInvalidBootImagePackage();
|
||||
}
|
||||
ON_SCOPE_EXIT { fclose(fp); };
|
||||
/* Open the file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY_CATCH(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)) {
|
||||
R_CONVERT(fs::ResultPathNotFound, ResultInvalidBootImagePackage())
|
||||
} R_END_TRY_CATCH;
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
Sha256Context sha_ctx;
|
||||
sha256ContextCreate(&sha_ctx);
|
||||
/* Read in chunks, hashing as we go. */
|
||||
crypto::Sha256Generator generator;
|
||||
generator.Initialize();
|
||||
|
||||
size_t total_size = 0;
|
||||
while (true) {
|
||||
size_t read_size = fread(work_buffer, 1, work_buffer_size, fp);
|
||||
if (ferror(fp)) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
if (read_size == 0) {
|
||||
break;
|
||||
}
|
||||
size_t size;
|
||||
R_TRY(fs::ReadFile(std::addressof(size), file, total_size, work_buffer, work_buffer_size, fs::ReadOption()));
|
||||
|
||||
sha256ContextUpdate(&sha_ctx, work_buffer, read_size);
|
||||
total_size += read_size;
|
||||
if (read_size != work_buffer_size) {
|
||||
generator.Update(work_buffer, size);
|
||||
total_size += size;
|
||||
|
||||
if (size != work_buffer_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sha256ContextGetHash(&sha_ctx, dst_hash);
|
||||
generator.GetHash(dst_hash, crypto::Sha256Generator::HashSize);
|
||||
*out_size = total_size;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <sys/stat.h>
|
||||
#include "updater_paths.hpp"
|
||||
|
||||
namespace ams::updater {
|
||||
|
@ -21,7 +20,7 @@ namespace ams::updater {
|
|||
namespace {
|
||||
|
||||
/* Actual paths. */
|
||||
constexpr const char *BootImagePackageMountPath = "bip";
|
||||
constexpr const char *BootImagePackageMountName = "bip";
|
||||
constexpr const char *BctPathNx = "bip:/nx/bct";
|
||||
constexpr const char *Package1PathNx = "bip:/nx/package1";
|
||||
constexpr const char *Package2PathNx = "bip:/nx/package2";
|
||||
|
@ -33,12 +32,12 @@ namespace ams::updater {
|
|||
AMS_ABORT_UNLESS(num_candidates > 0);
|
||||
|
||||
for (size_t i = 0; i < num_candidates; i++) {
|
||||
struct stat buf;
|
||||
if (stat(candidates[i], &buf) != 0) {
|
||||
fs::DirectoryEntryType type;
|
||||
if (R_FAILED(fs::GetEntryType(std::addressof(type), candidates[i]))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!S_ISREG(buf.st_mode)) {
|
||||
if (type != fs::DirectoryEntryType_File) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -51,8 +50,8 @@ namespace ams::updater {
|
|||
|
||||
}
|
||||
|
||||
const char *GetBootImagePackageMountPath() {
|
||||
return BootImagePackageMountPath;
|
||||
const char *GetMountName() {
|
||||
return BootImagePackageMountName;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
namespace ams::updater {
|
||||
|
||||
/* Path functionality. */
|
||||
const char *GetBootImagePackageMountPath();
|
||||
const char *GetMountName();
|
||||
const char *GetBctPath(BootImageUpdateType boot_image_update_type);
|
||||
const char *GetPackage1Path(BootImageUpdateType boot_image_update_type);
|
||||
const char *GetPackage2Path(BootImageUpdateType boot_image_update_type);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue