[WIP] NCCHContainer: support for partitions if container is NCSD (#5345)

* GetProgramLaunchInfo: improve to for 3ds files

* NCSD: allow to load other partitions

* fix typo

* Update src/core/hle/service/fs/fs_user.cpp

Co-authored-by: Valentin Vanelslande <vvanelslandedev@gmail.com>

* Update src/core/hle/service/fs/fs_user.cpp

Co-authored-by: Valentin Vanelslande <vvanelslandedev@gmail.com>

Co-authored-by: Marshall Mohror <mohror64@gmail.com>
Co-authored-by: Valentin Vanelslande <vvanelslandedev@gmail.com>
This commit is contained in:
Ben 2020-07-14 16:14:30 +02:00 committed by GitHub
parent 897e473da4
commit 1722701c07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 226 additions and 34 deletions

View file

@ -90,7 +90,7 @@ ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path,
std::string file_path = std::string file_path =
Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index); Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index);
auto ncch_container = NCCHContainer(file_path); auto ncch_container = NCCHContainer(file_path, 0, openfile_path.content_index);
Loader::ResultStatus result; Loader::ResultStatus result;
std::unique_ptr<FileBackend> file; std::unique_ptr<FileBackend> file;

View file

@ -114,14 +114,16 @@ static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decom
return true; return true;
} }
NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset) NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset, u32 partition)
: ncch_offset(ncch_offset), filepath(filepath) { : ncch_offset(ncch_offset), partition(partition), filepath(filepath) {
file = FileUtil::IOFile(filepath, "rb"); file = FileUtil::IOFile(filepath, "rb");
} }
Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 ncch_offset) { Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 ncch_offset,
u32 partition) {
this->filepath = filepath; this->filepath = filepath;
this->ncch_offset = ncch_offset; this->ncch_offset = ncch_offset;
this->partition = partition;
file = FileUtil::IOFile(filepath, "rb"); file = FileUtil::IOFile(filepath, "rb");
if (!file.IsOpen()) { if (!file.IsOpen()) {
@ -150,8 +152,13 @@ Loader::ResultStatus NCCHContainer::LoadHeader() {
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!"); NCSD_Header ncsd_header;
ncch_offset += 0x4000; file.Seek(ncch_offset, SEEK_SET);
file.ReadBytes(&ncsd_header, sizeof(NCSD_Header));
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
ASSERT(partition < 8);
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
LOG_ERROR(Service_FS, "{}", ncch_offset);
file.Seek(ncch_offset, SEEK_SET); file.Seek(ncch_offset, SEEK_SET);
file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
} }
@ -178,8 +185,12 @@ Loader::ResultStatus NCCHContainer::Load() {
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!"); NCSD_Header ncsd_header;
ncch_offset += 0x4000; file.Seek(ncch_offset, SEEK_SET);
file.ReadBytes(&ncsd_header, sizeof(NCSD_Header));
ASSERT(Loader::MakeMagic('N', 'C', 'S', 'D') == ncsd_header.magic);
ASSERT(partition < 8);
ncch_offset = ncsd_header.partitions[partition].offset * kBlockSize;
file.Seek(ncch_offset, SEEK_SET); file.Seek(ncch_offset, SEEK_SET);
file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
} }

View file

@ -15,6 +15,31 @@
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/romfs_reader.h" #include "core/file_sys/romfs_reader.h"
enum NCSDContentIndex { Main = 0, Manual = 1, DLP = 2, New3DSUpdate = 6, Update = 7 };
struct NCSD_Partitions {
u32 offset;
u32 size;
};
struct NCSD_Header {
u8 signature[0x100];
u32_le magic;
u32_le media_size;
u8 media_id[8];
u8 partition_fs_type[8];
u8 partition_crypt_type[8];
NCSD_Partitions partitions[8];
u8 extended_header_hash[0x20];
u32_le additional_header_size;
u32_le sector_zero_offset;
u8 partition_flags[8];
u8 partition_id_table[0x40];
u8 reserved[0x30];
};
static_assert(sizeof(NCSD_Header) == 0x200, "NCCH header structure size is wrong");
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
/// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) /// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym)
@ -209,10 +234,11 @@ namespace FileSys {
*/ */
class NCCHContainer { class NCCHContainer {
public: public:
NCCHContainer(const std::string& filepath, u32 ncch_offset = 0); NCCHContainer(const std::string& filepath, u32 ncch_offset = 0, u32 partition = 0);
NCCHContainer() {} NCCHContainer() {}
Loader::ResultStatus OpenFile(const std::string& filepath, u32 ncch_offset = 0); Loader::ResultStatus OpenFile(const std::string& filepath, u32 ncch_offset = 0,
u32 partition = 0);
/** /**
* Ensure NCCH header is loaded and ready for reading sections * Ensure NCCH header is loaded and ready for reading sections
@ -339,6 +365,7 @@ private:
u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs
u32 exefs_offset = 0; u32 exefs_offset = 0;
u32 partition = 0;
std::string filepath; std::string filepath;
FileUtil::IOFile file; FileUtil::IOFile file;

View file

@ -31,6 +31,7 @@
#include "core/hle/service/am/am_sys.h" #include "core/hle/service/am/am_sys.h"
#include "core/hle/service/am/am_u.h" #include "core/hle/service/am/am_u.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "core/loader/smdh.h" #include "core/loader/smdh.h"
@ -465,16 +466,19 @@ std::string GetTitleMetadataPath(Service::FS::MediaType media_type, u64 tid, boo
return content_path + fmt::format("{:08x}.tmd", (update ? update_id : base_id)); return content_path + fmt::format("{:08x}.tmd", (update ? update_id : base_id));
} }
std::string GetTitleContentPath(FS::MediaType media_type, u64 tid, std::size_t index, bool update) { std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, std::size_t index,
std::string content_path = GetTitlePath(media_type, tid) + "content/"; bool update) {
if (media_type == FS::MediaType::GameCard) { if (media_type == Service::FS::MediaType::GameCard) {
// TODO(shinyquagsire23): get current app file if TID matches? // TODO(B3N30): check if TID matches
LOG_ERROR(Service_AM, "Request for gamecard partition {} content path unimplemented!", auto fs_user =
static_cast<u32>(index)); Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
return ""; "fs:USER");
return fs_user->GetCurrentGamecardPath();
} }
std::string content_path = GetTitlePath(media_type, tid) + "content/";
std::string tmd_path = GetTitleMetadataPath(media_type, tid, update); std::string tmd_path = GetTitleMetadataPath(media_type, tid, update);
u32 content_id = 0; u32 content_id = 0;
@ -509,9 +513,11 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid) {
return fmt::format("{}{:08x}/{:08x}/", GetMediaTitlePath(media_type), high, low); return fmt::format("{}{:08x}/{:08x}/", GetMediaTitlePath(media_type), high, low);
if (media_type == Service::FS::MediaType::GameCard) { if (media_type == Service::FS::MediaType::GameCard) {
// TODO(shinyquagsire23): get current app path if TID matches? // TODO(B3N30): check if TID matches
LOG_ERROR(Service_AM, "Request for gamecard title path unimplemented!"); auto fs_user =
return ""; Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
"fs:USER");
return fs_user->GetCurrentGamecardPath();
} }
return ""; return "";
@ -528,9 +534,11 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
SDCARD_ID); SDCARD_ID);
if (media_type == Service::FS::MediaType::GameCard) { if (media_type == Service::FS::MediaType::GameCard) {
// TODO(shinyquagsire23): get current app parent folder if TID matches? // TODO(B3N30): check if TID matchess
LOG_ERROR(Service_AM, "Request for gamecard parent path unimplemented!"); auto fs_user =
return ""; Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
"fs:USER");
return fs_user->GetCurrentGamecardPath();
} }
return ""; return "";

View file

@ -31,6 +31,16 @@
namespace Service::FS { namespace Service::FS {
MediaType GetMediaTypeFromPath(std::string_view path) {
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 0) == 0) {
return MediaType::NAND;
}
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 0) == 0) {
return MediaType::SDMC;
}
return MediaType::GameCard;
}
ArchiveBackend* ArchiveManager::GetArchive(ArchiveHandle handle) { ArchiveBackend* ArchiveManager::GetArchive(ArchiveHandle handle) {
auto itr = handle_map.find(handle); auto itr = handle_map.find(handle);
return (itr == handle_map.end()) ? nullptr : itr->second.get(); return (itr == handle_map.end()) ? nullptr : itr->second.get();

View file

@ -48,6 +48,14 @@ enum class ArchiveIdCode : u32 {
/// Media types for the archives /// Media types for the archives
enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 }; enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 };
MediaType GetMediaTypeFromPath(std::string_view path);
enum class SpecialContentType : u8 {
Update = 1,
Manual = 2,
DLPChild = 3,
};
typedef u64 ArchiveHandle; typedef u64 ArchiveHandle;
struct ArchiveResource { struct ArchiveResource {

View file

@ -12,6 +12,7 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/ncch_container.h"
#include "core/file_sys/seed_db.h" #include "core/file_sys/seed_db.h"
#include "core/hle/ipc.h" #include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
@ -682,13 +683,11 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "process_id={}", process_id); LOG_DEBUG(Service_FS, "process_id={}", process_id);
// TODO(Subv): The real FS service manages its own process list and only checks the processes auto program_info = program_info_map.find(process_id);
// that were registered with the 'fs:REG' service.
auto process = system.Kernel().GetProcessById(process_id);
IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); IPC::RequestBuilder rb = rp.MakeBuilder(5, 0);
if (process == nullptr) { if (program_info == program_info_map.end()) {
// Note: In this case, the rest of the parameters are not changed but the command header // Note: In this case, the rest of the parameters are not changed but the command header
// remains the same. // remains the same.
rb.Push(ResultCode(FileSys::ErrCodes::ArchiveNotMounted, ErrorModule::FS, rb.Push(ResultCode(FileSys::ErrCodes::ArchiveNotMounted, ErrorModule::FS,
@ -697,13 +696,9 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
return; return;
} }
u64 program_id = process->codeset->program_id;
auto media_type = Service::AM::GetTitleMediaType(program_id);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push(program_id); rb.Push(program_info->second.program_id);
rb.Push(static_cast<u8>(media_type)); rb.Push(static_cast<u8>(program_info->second.media_type));
// TODO(Subv): Find out what this value means. // TODO(Subv): Find out what this value means.
rb.Push<u32>(0); rb.Push<u32>(0);
@ -752,6 +747,32 @@ void FS_USER::ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx) {
static_cast<u32>(media_type)); static_cast<u32>(media_type));
} }
void FS_USER::GetSpecialContentIndex(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x83A, 4, 0);
const MediaType media_type = static_cast<MediaType>(rp.Pop<u8>());
const u64 title_id = rp.Pop<u64>();
const auto type = rp.PopEnum<SpecialContentType>();
LOG_DEBUG(Service_FS, "called, media_type={:08X} type={:08X}, title_id={:016X}",
static_cast<u32>(media_type), static_cast<u32>(type), title_id);
ResultVal<u16> index;
if (media_type == MediaType::GameCard) {
index = GetSpecialContentIndexFromGameCard(title_id, type);
} else {
index = GetSpecialContentIndexFromTMD(media_type, title_id, type);
}
if (index.Succeeded()) {
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.Push(index.Unwrap());
} else {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(index.Code());
}
}
void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) { void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb{ctx, 0x87D, 2, 0}; IPC::RequestBuilder rb{ctx, 0x87D, 2, 0};
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
@ -808,6 +829,66 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
rb.Push<u64>(0); // the secure value rb.Push<u64>(0); // the secure value
} }
void FS_USER::Register(u32 process_id, u64 program_id, const std::string& filepath) {
const MediaType media_type = GetMediaTypeFromPath(filepath);
program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type});
if (media_type == MediaType::GameCard) {
current_gamecard_path = filepath;
}
}
std::string FS_USER::GetCurrentGamecardPath() const {
return current_gamecard_path;
}
ResultVal<u16> FS_USER::GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type) {
// TODO(B3N30) check if on real 3DS NCSD is checked if partition exists
if (type > SpecialContentType::DLPChild) {
// Maybe type 4 is New 3DS update/partition 6 but this needs more research
// TODO(B3N30): Find correct result code
return ResultCode(-1);
}
switch (type) {
case SpecialContentType::Update:
return MakeResult(static_cast<u16>(NCSDContentIndex::Update));
case SpecialContentType::Manual:
return MakeResult(static_cast<u16>(NCSDContentIndex::Manual));
case SpecialContentType::DLPChild:
return MakeResult(static_cast<u16>(NCSDContentIndex::DLP));
default:
ASSERT(false);
}
}
ResultVal<u16> FS_USER::GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id,
SpecialContentType type) {
if (type > SpecialContentType::DLPChild) {
// TODO(B3N30): Find correct result code
return ResultCode(-1);
}
std::string tmd_path = AM::GetTitleMetadataPath(media_type, title_id);
FileSys::TitleMetadata tmd;
if (tmd.Load(tmd_path) != Loader::ResultStatus::Success || type == SpecialContentType::Update) {
// TODO(B3N30): Find correct result code
return ResultCode(-1);
}
// TODO(B3N30): Does real 3DS check if content exists in TMD?
switch (type) {
case SpecialContentType::Manual:
return MakeResult(static_cast<u16>(FileSys::TMDContentIndex::Manual));
case SpecialContentType::DLPChild:
return MakeResult(static_cast<u16>(FileSys::TMDContentIndex::DLP));
default:
ASSERT(false);
}
}
FS_USER::FS_USER(Core::System& system) FS_USER::FS_USER(Core::System& system)
: ServiceFramework("fs:USER", 30), system(system), archives(system.ArchiveManager()) { : ServiceFramework("fs:USER", 30), system(system), archives(system.ArchiveManager()) {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
@ -870,7 +951,7 @@ FS_USER::FS_USER(Core::System& system)
{0x08370040, nullptr, "SetCardSpiBaudRate"}, {0x08370040, nullptr, "SetCardSpiBaudRate"},
{0x08380040, nullptr, "SetCardSpiBusMode"}, {0x08380040, nullptr, "SetCardSpiBusMode"},
{0x08390000, nullptr, "SendInitializeInfoTo9"}, {0x08390000, nullptr, "SendInitializeInfoTo9"},
{0x083A0100, nullptr, "GetSpecialContentIndex"}, {0x083A0100, &FS_USER::GetSpecialContentIndex, "GetSpecialContentIndex"},
{0x083B00C2, nullptr, "GetLegacyRomHeader"}, {0x083B00C2, nullptr, "GetLegacyRomHeader"},
{0x083C00C2, nullptr, "GetLegacyBannerData"}, {0x083C00C2, nullptr, "GetLegacyBannerData"},
{0x083D0100, nullptr, "CheckAuthorityToAccessExtSaveData"}, {0x083D0100, nullptr, "CheckAuthorityToAccessExtSaveData"},

View file

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <unordered_map>
#include <boost/serialization/base_object.hpp> #include <boost/serialization/base_object.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -38,6 +39,12 @@ class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
public: public:
explicit FS_USER(Core::System& system); explicit FS_USER(Core::System& system);
// On real HW this is part of FS:Reg. But since that module is only used by loader and pm, which
// we HLEed, we can just directly use it here
void Register(u32 process_id, u64 program_id, const std::string& filepath);
std::string GetCurrentGamecardPath() const;
private: private:
void Initialize(Kernel::HLERequestContext& ctx); void Initialize(Kernel::HLERequestContext& ctx);
@ -525,6 +532,20 @@ private:
*/ */
void ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx); void ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx);
/**
* FS_User::GetSpecialContentIndex service function.
* Inputs:
* 0 : 0x083A0100
* 1 : Media type
* 2-3 : Program ID
* 4 : Special content type
* Outputs:
* 0 : 0x083A0080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Special content index
*/
void GetSpecialContentIndex(Kernel::HLERequestContext& ctx);
/** /**
* FS_User::GetNumSeeds service function. * FS_User::GetNumSeeds service function.
* Inputs: * Inputs:
@ -577,6 +598,18 @@ private:
*/ */
void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx); void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
static ResultVal<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
static ResultVal<u16> GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id,
SpecialContentType type);
struct ProgramInfo {
u64 program_id;
MediaType media_type;
};
std::unordered_map<u32, ProgramInfo> program_info_map;
std::string current_gamecard_path;
u32 priority = -1; ///< For SetPriority and GetPriority service functions u32 priority = -1; ///< For SetPriority and GetPriority service functions
Core::System& system; Core::System& system;

View file

@ -9,6 +9,7 @@
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/loader/3dsx.h" #include "core/loader/3dsx.h"
#include "core/memory.h" #include "core/memory.h"
@ -274,6 +275,11 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr<Kernel::Process>& process)
process->resource_limit = Core::System::GetInstance().Kernel().ResourceLimit().GetForCategory( process->resource_limit = Core::System::GetInstance().Kernel().ResourceLimit().GetForCategory(
Kernel::ResourceLimitCategory::APPLICATION); Kernel::ResourceLimitCategory::APPLICATION);
// On real HW this is done with FS:Reg, but we can be lazy
auto fs_user =
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
fs_user->Register(process->GetObjectId(), process->codeset->program_id, filepath);
process->Run(48, Kernel::DEFAULT_STACK_SIZE); process->Run(48, Kernel::DEFAULT_STACK_SIZE);
Core::System::GetInstance().ArchiveManager().RegisterSelfNCCH(*this); Core::System::GetInstance().ArchiveManager().RegisterSelfNCCH(*this);

View file

@ -21,6 +21,7 @@
#include "core/hle/service/am/am.h" #include "core/hle/service/am/am.h"
#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/loader/ncch.h" #include "core/loader/ncch.h"
#include "core/loader/smdh.h" #include "core/loader/smdh.h"
#include "core/memory.h" #include "core/memory.h"
@ -142,6 +143,13 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process)
s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority;
u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size;
// On real HW this is done with FS:Reg, but we can be lazy
auto fs_user =
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
"fs:USER");
fs_user->Register(process->process_id, process->codeset->program_id, filepath);
process->Run(priority, stack_size); process->Run(priority, stack_size);
return ResultStatus::Success; return ResultStatus::Success;
} }