From 1722701c07088593f1db605ff97d9d6865899f5e Mon Sep 17 00:00:00 2001
From: Ben <bene_thomas@web.de>
Date: Tue, 14 Jul 2020 16:14:30 +0200
Subject: [PATCH] [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>
---
 src/core/file_sys/archive_ncch.cpp   |   2 +-
 src/core/file_sys/ncch_container.cpp |  25 +++++--
 src/core/file_sys/ncch_container.h   |  31 +++++++-
 src/core/hle/service/am/am.cpp       |  34 +++++----
 src/core/hle/service/fs/archive.cpp  |  10 +++
 src/core/hle/service/fs/archive.h    |   8 +++
 src/core/hle/service/fs/fs_user.cpp  | 103 ++++++++++++++++++++++++---
 src/core/hle/service/fs/fs_user.h    |  33 +++++++++
 src/core/loader/3dsx.cpp             |   6 ++
 src/core/loader/ncch.cpp             |   8 +++
 10 files changed, 226 insertions(+), 34 deletions(-)

diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp
index ae2e97535..b658e9a29 100644
--- a/src/core/file_sys/archive_ncch.cpp
+++ b/src/core/file_sys/archive_ncch.cpp
@@ -90,7 +90,7 @@ ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path,
 
     std::string file_path =
         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;
     std::unique_ptr<FileBackend> file;
diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp
index cb8a2d513..831c930fc 100644
--- a/src/core/file_sys/ncch_container.cpp
+++ b/src/core/file_sys/ncch_container.cpp
@@ -114,14 +114,16 @@ static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decom
     return true;
 }
 
-NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset)
-    : ncch_offset(ncch_offset), filepath(filepath) {
+NCCHContainer::NCCHContainer(const std::string& filepath, u32 ncch_offset, u32 partition)
+    : ncch_offset(ncch_offset), partition(partition), filepath(filepath) {
     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->ncch_offset = ncch_offset;
+    this->partition = partition;
     file = FileUtil::IOFile(filepath, "rb");
 
     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)...
     if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
-        LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!");
-        ncch_offset += 0x4000;
+        NCSD_Header ncsd_header;
+        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.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)...
         if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
-            LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!");
-            ncch_offset += 0x4000;
+            NCSD_Header ncsd_header;
+            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.ReadBytes(&ncch_header, sizeof(NCCH_Header));
         }
diff --git a/src/core/file_sys/ncch_container.h b/src/core/file_sys/ncch_container.h
index 484218636..9db1d90be 100644
--- a/src/core/file_sys/ncch_container.h
+++ b/src/core/file_sys/ncch_container.h
@@ -15,6 +15,31 @@
 #include "core/core.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)
 
@@ -209,10 +234,11 @@ namespace FileSys {
  */
 class NCCHContainer {
 public:
-    NCCHContainer(const std::string& filepath, u32 ncch_offset = 0);
+    NCCHContainer(const std::string& filepath, u32 ncch_offset = 0, u32 partition = 0);
     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
@@ -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 exefs_offset = 0;
+    u32 partition = 0;
 
     std::string filepath;
     FileUtil::IOFile file;
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 295269c27..c51c611f8 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -31,6 +31,7 @@
 #include "core/hle/service/am/am_sys.h"
 #include "core/hle/service/am/am_u.h"
 #include "core/hle/service/fs/archive.h"
+#include "core/hle/service/fs/fs_user.h"
 #include "core/loader/loader.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));
 }
 
-std::string GetTitleContentPath(FS::MediaType media_type, u64 tid, std::size_t index, bool update) {
-    std::string content_path = GetTitlePath(media_type, tid) + "content/";
+std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, std::size_t index,
+                                bool update) {
 
-    if (media_type == FS::MediaType::GameCard) {
-        // TODO(shinyquagsire23): get current app file if TID matches?
-        LOG_ERROR(Service_AM, "Request for gamecard partition {} content path unimplemented!",
-                  static_cast<u32>(index));
-        return "";
+    if (media_type == Service::FS::MediaType::GameCard) {
+        // TODO(B3N30): check if TID matches
+        auto fs_user =
+            Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
+                "fs:USER");
+        return fs_user->GetCurrentGamecardPath();
     }
 
+    std::string content_path = GetTitlePath(media_type, tid) + "content/";
+
     std::string tmd_path = GetTitleMetadataPath(media_type, tid, update);
 
     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);
 
     if (media_type == Service::FS::MediaType::GameCard) {
-        // TODO(shinyquagsire23): get current app path if TID matches?
-        LOG_ERROR(Service_AM, "Request for gamecard title path unimplemented!");
-        return "";
+        // TODO(B3N30): check if TID matches
+        auto fs_user =
+            Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
+                "fs:USER");
+        return fs_user->GetCurrentGamecardPath();
     }
 
     return "";
@@ -528,9 +534,11 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
                            SDCARD_ID);
 
     if (media_type == Service::FS::MediaType::GameCard) {
-        // TODO(shinyquagsire23): get current app parent folder if TID matches?
-        LOG_ERROR(Service_AM, "Request for gamecard parent path unimplemented!");
-        return "";
+        // TODO(B3N30): check if TID matchess
+        auto fs_user =
+            Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
+                "fs:USER");
+        return fs_user->GetCurrentGamecardPath();
     }
 
     return "";
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index bca2d7934..28121d7f0 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -31,6 +31,16 @@
 
 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) {
     auto itr = handle_map.find(handle);
     return (itr == handle_map.end()) ? nullptr : itr->second.get();
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 4ce61a3c1..b11167807 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -48,6 +48,14 @@ enum class ArchiveIdCode : u32 {
 /// Media types for the archives
 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;
 
 struct ArchiveResource {
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index 831cecca2..ed8ede529 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -12,6 +12,7 @@
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/errors.h"
+#include "core/file_sys/ncch_container.h"
 #include "core/file_sys/seed_db.h"
 #include "core/hle/ipc.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);
 
-    // TODO(Subv): The real FS service manages its own process list and only checks the processes
-    // that were registered with the 'fs:REG' service.
-    auto process = system.Kernel().GetProcessById(process_id);
+    auto program_info = program_info_map.find(process_id);
 
     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
         // remains the same.
         rb.Push(ResultCode(FileSys::ErrCodes::ArchiveNotMounted, ErrorModule::FS,
@@ -697,13 +696,9 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
         return;
     }
 
-    u64 program_id = process->codeset->program_id;
-
-    auto media_type = Service::AM::GetTitleMediaType(program_id);
-
     rb.Push(RESULT_SUCCESS);
-    rb.Push(program_id);
-    rb.Push(static_cast<u8>(media_type));
+    rb.Push(program_info->second.program_id);
+    rb.Push(static_cast<u8>(program_info->second.media_type));
 
     // TODO(Subv): Find out what this value means.
     rb.Push<u32>(0);
@@ -752,6 +747,32 @@ void FS_USER::ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx) {
               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) {
     IPC::RequestBuilder rb{ctx, 0x87D, 2, 0};
     rb.Push(RESULT_SUCCESS);
@@ -808,6 +829,66 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
     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)
     : ServiceFramework("fs:USER", 30), system(system), archives(system.ArchiveManager()) {
     static const FunctionInfo functions[] = {
@@ -870,7 +951,7 @@ FS_USER::FS_USER(Core::System& system)
         {0x08370040, nullptr, "SetCardSpiBaudRate"},
         {0x08380040, nullptr, "SetCardSpiBusMode"},
         {0x08390000, nullptr, "SendInitializeInfoTo9"},
-        {0x083A0100, nullptr, "GetSpecialContentIndex"},
+        {0x083A0100, &FS_USER::GetSpecialContentIndex, "GetSpecialContentIndex"},
         {0x083B00C2, nullptr, "GetLegacyRomHeader"},
         {0x083C00C2, nullptr, "GetLegacyBannerData"},
         {0x083D0100, nullptr, "CheckAuthorityToAccessExtSaveData"},
diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h
index 243b8f858..3962b8d6a 100644
--- a/src/core/hle/service/fs/fs_user.h
+++ b/src/core/hle/service/fs/fs_user.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <unordered_map>
 #include <boost/serialization/base_object.hpp>
 #include "common/common_types.h"
 #include "core/hle/service/service.h"
@@ -38,6 +39,12 @@ class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
 public:
     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:
     void Initialize(Kernel::HLERequestContext& ctx);
 
@@ -525,6 +532,20 @@ private:
      */
     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.
      *  Inputs:
@@ -577,6 +598,18 @@ private:
      */
     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
 
     Core::System& system;
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp
index 84321011b..40c2bd1fb 100644
--- a/src/core/loader/3dsx.cpp
+++ b/src/core/loader/3dsx.cpp
@@ -9,6 +9,7 @@
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/resource_limit.h"
 #include "core/hle/service/fs/archive.h"
+#include "core/hle/service/fs/fs_user.h"
 #include "core/loader/3dsx.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(
         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);
 
     Core::System::GetInstance().ArchiveManager().RegisterSelfNCCH(*this);
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index b2efe0198..6f32cc2eb 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -21,6 +21,7 @@
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/cfg/cfg.h"
 #include "core/hle/service/fs/archive.h"
+#include "core/hle/service/fs/fs_user.h"
 #include "core/loader/ncch.h"
 #include "core/loader/smdh.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;
         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);
         return ResultStatus::Success;
     }