mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-23 12:51:13 +00:00
pgl: implement LaunchProgramFromHost, GetHostContentMetaInfo
This commit is contained in:
parent
3b639b604d
commit
cfe0597385
4 changed files with 383 additions and 2 deletions
|
@ -23,6 +23,7 @@
|
||||||
namespace ams::pgl {
|
namespace ams::pgl {
|
||||||
|
|
||||||
enum LaunchFlags : u8 {
|
enum LaunchFlags : u8 {
|
||||||
|
LaunchFlags_None = 0,
|
||||||
LaunchFlags_EnableDetailedCrashReport = (1 << 0),
|
LaunchFlags_EnableDetailedCrashReport = (1 << 0),
|
||||||
LaunchFlags_EnableCrashReportScreenShotForProduction = (1 << 1),
|
LaunchFlags_EnableCrashReportScreenShotForProduction = (1 << 1),
|
||||||
LaunchFlags_EnableCrashReportScreenShotForDevelop = (1 << 2),
|
LaunchFlags_EnableCrashReportScreenShotForDevelop = (1 << 2),
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
/*
|
||||||
|
* 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 "pgl_srv_shell_host_utils.hpp"
|
||||||
|
#include "pgl_srv_shell.hpp"
|
||||||
|
|
||||||
|
namespace ams::pgl::srv {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr inline char HostPackageMountName[] = "HostPackageRead";
|
||||||
|
static_assert(sizeof(HostPackageMountName) - 1 <= fs::MountNameLengthMax);
|
||||||
|
|
||||||
|
struct CaseInsensitiveCharTraits : public std::char_traits<char> {
|
||||||
|
static char to_upper(char c) {
|
||||||
|
return std::toupper(static_cast<unsigned char>(c));
|
||||||
|
}
|
||||||
|
static bool eq(char c1, char c2) {
|
||||||
|
return to_upper(c1) == to_upper(c2);
|
||||||
|
}
|
||||||
|
static bool lt(char c1, char c2) {
|
||||||
|
return to_upper(c1) < to_upper(c2);
|
||||||
|
}
|
||||||
|
static int compare(const char *s1, const char *s2, size_t n) {
|
||||||
|
while ( n-- != 0 ) {
|
||||||
|
if ( to_upper(*s1) < to_upper(*s2) ) return -1;
|
||||||
|
if ( to_upper(*s1) > to_upper(*s2) ) return 1;
|
||||||
|
++s1; ++s2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
static const char *find(const char *s, int n, char a) {
|
||||||
|
auto const ua (to_upper(a));
|
||||||
|
while ( n-- != 0 )
|
||||||
|
{
|
||||||
|
if (to_upper(*s) == ua)
|
||||||
|
return s;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using PathView = std::basic_string_view<char, CaseInsensitiveCharTraits>;
|
||||||
|
|
||||||
|
enum class ExtensionType {
|
||||||
|
None = 0,
|
||||||
|
Nsp = 1,
|
||||||
|
Nspd = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool HasSuffix(const char *str, const char *suffix) {
|
||||||
|
const size_t suffix_len = std::strlen(suffix);
|
||||||
|
const size_t str_len = std::strlen(str);
|
||||||
|
if (suffix_len > str_len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (PathView(str).substr(str_len - suffix_len) == PathView(suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
class HostPackageReader {
|
||||||
|
NON_COPYABLE(HostPackageReader);
|
||||||
|
NON_MOVEABLE(HostPackageReader);
|
||||||
|
private:
|
||||||
|
char content_path[fs::EntryNameLengthMax] = {};
|
||||||
|
ExtensionType extension_type = ExtensionType::None;
|
||||||
|
char mount_name[fs::MountNameLengthMax] = {};
|
||||||
|
bool is_mounted = false;
|
||||||
|
ncm::AutoBuffer content_meta_buffer;
|
||||||
|
ncm::ProgramId program_id = ncm::InvalidProgramId;
|
||||||
|
u32 program_version = 0;
|
||||||
|
ncm::ContentMetaType content_meta_type = static_cast<ncm::ContentMetaType>(0);
|
||||||
|
u8 program_index = 0;
|
||||||
|
public:
|
||||||
|
HostPackageReader() : content_meta_buffer() { /* ... */ }
|
||||||
|
~HostPackageReader() {
|
||||||
|
if (this->is_mounted) {
|
||||||
|
fs::Unmount(this->mount_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Initialize(const char *package, const char *mount) {
|
||||||
|
/* Copy in the content path. */
|
||||||
|
R_UNLESS(strlen(package) <= sizeof(this->content_path) - 1, pgl::ResultBufferNotEnough());
|
||||||
|
std::strcpy(this->content_path, package);
|
||||||
|
|
||||||
|
/* Set the extension type. */
|
||||||
|
R_TRY(this->SetExtensionType());
|
||||||
|
|
||||||
|
/* Copy in mount name. */
|
||||||
|
R_UNLESS(strlen(mount) <= sizeof(this->mount_name) - 1, pgl::ResultBufferNotEnough());
|
||||||
|
std::strcpy(this->mount_name, mount);
|
||||||
|
|
||||||
|
/* Mount the package. */
|
||||||
|
R_TRY(fs::MountApplicationPackage(this->mount_name, this->content_path));
|
||||||
|
this->is_mounted = true;
|
||||||
|
|
||||||
|
/* Set the content meta buffer. */
|
||||||
|
R_TRY(this->SetContentMetaBuffer());
|
||||||
|
|
||||||
|
/* Ensure we have a content meta buffer. */
|
||||||
|
R_UNLESS(this->content_meta_buffer.Get() != nullptr, pgl::ResultContentMetaNotFound());
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ReadProgramInfo() {
|
||||||
|
/* First, read the program index. */
|
||||||
|
R_TRY(this->GetProgramIndex(std::addressof(this->program_index)));
|
||||||
|
|
||||||
|
/* Next, create a key for the rest of the fields. */
|
||||||
|
const auto key = ncm::PackagedContentMetaReader(this->content_meta_buffer.Get(), this->content_meta_buffer.GetSize()).GetKey();
|
||||||
|
|
||||||
|
/* Set fields. */
|
||||||
|
this->program_id = {key.id};
|
||||||
|
this->program_version = key.version;
|
||||||
|
this->content_meta_type = key.type;
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetContentPath(lr::Path *out, ncm::ContentType type, std::optional<u8> index) const {
|
||||||
|
switch (this->extension_type) {
|
||||||
|
case ExtensionType::Nsp: return this->GetContentPathInNsp(out, type, index);
|
||||||
|
case ExtensionType::Nspd: return this->GetContentPathInNspd(out, type, index);
|
||||||
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm::ProgramId GetProgramId() const {
|
||||||
|
return this->program_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetProgramVersion() const {
|
||||||
|
return this->program_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm::ContentMetaType GetContentMetaType() const {
|
||||||
|
return this->content_meta_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 GetProgramIndex() const {
|
||||||
|
return this->program_index;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Result GetContentPathInNsp(lr::Path *out, ncm::ContentType type, std::optional<u8> index) const {
|
||||||
|
/* Create a reader. */
|
||||||
|
auto reader = ncm::PackagedContentMetaReader(this->content_meta_buffer.Get(), this->content_meta_buffer.GetSize());
|
||||||
|
|
||||||
|
/* Get the content info. */
|
||||||
|
const ncm::PackagedContentInfo *content_info = nullptr;
|
||||||
|
if (index) {
|
||||||
|
content_info = reader.GetContentInfo(type, *index);
|
||||||
|
} else {
|
||||||
|
content_info = reader.GetContentInfo(type);
|
||||||
|
}
|
||||||
|
R_UNLESS(content_info != nullptr, pgl::ResultApplicationContentNotFound());
|
||||||
|
|
||||||
|
/* Get the content id string. */
|
||||||
|
ncm::ContentIdString id_str;
|
||||||
|
ncm::GetStringFromContentId(id_str.data, sizeof(id_str.data), content_info->GetId());
|
||||||
|
|
||||||
|
/* Get the file name. */
|
||||||
|
char file_name[ncm::ContentIdStringLength + 5];
|
||||||
|
const size_t len = std::snprintf(file_name, sizeof(file_name), "%s.nca", id_str.data);
|
||||||
|
R_UNLESS(len + 1 == sizeof(file_name), pgl::ResultBufferNotEnough());
|
||||||
|
|
||||||
|
/* Ensure we have the content. */
|
||||||
|
bool has_content;
|
||||||
|
R_TRY(this->SearchContent(std::addressof(has_content), out, file_name, fs::OpenDirectoryMode_File));
|
||||||
|
R_UNLESS(has_content, pgl::ResultApplicationContentNotFound());
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetContentPathInNspd(lr::Path *out, ncm::ContentType type, std::optional<u8> index) const {
|
||||||
|
/* Get the content name. */
|
||||||
|
const char *content_name = nullptr;
|
||||||
|
switch (type) {
|
||||||
|
case ncm::ContentType::Program: content_name = "program"; break;
|
||||||
|
case ncm::ContentType::Control: content_name = "control"; break;
|
||||||
|
case ncm::ContentType::HtmlDocument: content_name = "htmlDocument"; break;
|
||||||
|
case ncm::ContentType::LegalInformation: content_name = "legalInformation"; break;
|
||||||
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the file name. */
|
||||||
|
/* NSPD does not support indexed content, so we always use 0 as the index. */
|
||||||
|
char file_name[0x20];
|
||||||
|
const size_t len = std::snprintf(file_name, sizeof(file_name), "%s%d.ncd", content_name, 0);
|
||||||
|
R_UNLESS(len + 1 <= sizeof(file_name), pgl::ResultBufferNotEnough());
|
||||||
|
|
||||||
|
/* Ensure we have the content. */
|
||||||
|
bool has_content;
|
||||||
|
R_TRY(this->SearchContent(std::addressof(has_content), out, file_name, fs::OpenDirectoryMode_Directory));
|
||||||
|
R_UNLESS(has_content, pgl::ResultApplicationContentNotFound());
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetProgramIndex(u8 *out) {
|
||||||
|
/* Nspd programs do not have indices. */
|
||||||
|
if (this->extension_type == ExtensionType::Nspd) {
|
||||||
|
*out = 0;
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a reader. */
|
||||||
|
auto reader = ncm::PackagedContentMetaReader(this->content_meta_buffer.Get(), this->content_meta_buffer.GetSize());
|
||||||
|
|
||||||
|
/* Get the program content info. */
|
||||||
|
auto program_content_info = reader.GetContentInfo(ncm::ContentType::Program);
|
||||||
|
R_UNLESS(program_content_info, pgl::ResultApplicationContentNotFound());
|
||||||
|
|
||||||
|
/* Return the index. */
|
||||||
|
*out = program_content_info->GetIdOffset();
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SetExtensionType() {
|
||||||
|
/* First, clear the suffix if the path is a program ncd. */
|
||||||
|
if (HasSuffix(this->content_path, "program0.ncd/")) {
|
||||||
|
this->content_path[strnlen(this->content_path, sizeof(this->content_path)) - std::strlen("program0.ncd/")] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasSuffix(this->content_path, ".nsp")) {
|
||||||
|
this->extension_type = ExtensionType::Nsp;
|
||||||
|
return ResultSuccess();
|
||||||
|
} else if (HasSuffix(this->content_path, ".nspd") || HasSuffix(this->content_path, ".nspd/")) {
|
||||||
|
this->extension_type = ExtensionType::Nspd;
|
||||||
|
return ResultSuccess();
|
||||||
|
} else {
|
||||||
|
return fs::ResultPathNotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SetContentMetaBuffer() {
|
||||||
|
constexpr const char ContentMetaFileExtension[] = ".cnmt.nca";
|
||||||
|
constexpr const char ContentMetaDirectoryExtension[] = "meta0.ncd";
|
||||||
|
|
||||||
|
/* Find the Content meta path. */
|
||||||
|
bool has_content = false;
|
||||||
|
lr::Path meta_path;
|
||||||
|
switch (this->extension_type) {
|
||||||
|
case ExtensionType::Nsp: R_TRY(this->SearchContent(std::addressof(has_content), std::addressof(meta_path), ContentMetaFileExtension, fs::OpenDirectoryMode_File)); break;
|
||||||
|
case ExtensionType::Nspd: R_TRY(this->SearchContent(std::addressof(has_content), std::addressof(meta_path), ContentMetaDirectoryExtension, fs::OpenDirectoryMode_Directory)); break;
|
||||||
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||||
|
}
|
||||||
|
R_UNLESS(has_content, pgl::ResultContentMetaNotFound());
|
||||||
|
|
||||||
|
/* Read the content meta buffer. */
|
||||||
|
return ncm::ReadContentMetaPath(std::addressof(this->content_meta_buffer), meta_path.str);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SearchContent(bool *out, lr::Path *out_path, const char *extension, fs::OpenDirectoryMode mode) const {
|
||||||
|
/* Generate the root directory path. */
|
||||||
|
char root_dir[sizeof(this->mount_name) + 2];
|
||||||
|
std::snprintf(root_dir, sizeof(root_dir), "%s:/", this->mount_name);
|
||||||
|
|
||||||
|
/* Open the root directory. */
|
||||||
|
fs::DirectoryHandle dir;
|
||||||
|
R_TRY(fs::OpenDirectory(std::addressof(dir), root_dir, mode));
|
||||||
|
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
|
||||||
|
|
||||||
|
/* Iterate over directory entries. */
|
||||||
|
while (true) {
|
||||||
|
fs::DirectoryEntry entry;
|
||||||
|
s64 count;
|
||||||
|
R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1));
|
||||||
|
if (count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we match the suffix. */
|
||||||
|
if (HasSuffix(entry.name, extension)) {
|
||||||
|
*out = true;
|
||||||
|
if (out_path) {
|
||||||
|
const size_t len = std::snprintf(out_path->str, sizeof(out_path->str), "%s/%s", this->content_path, entry.name);
|
||||||
|
R_UNLESS(len + 1 < sizeof(out_path->str), pgl::ResultBufferNotEnough());
|
||||||
|
if (entry.type == fs::DirectoryEntryType_Directory) {
|
||||||
|
out_path->str[len] = '/';
|
||||||
|
out_path->str[len + 1] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We didn't find a match. */
|
||||||
|
*out = false;
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LaunchProgramFromHost(os::ProcessId *out, const char *package_path, u32 pm_flags) {
|
||||||
|
/* Read the package. */
|
||||||
|
HostPackageReader reader;
|
||||||
|
R_TRY(reader.Initialize(package_path, HostPackageMountName));
|
||||||
|
|
||||||
|
/* Read the program info. */
|
||||||
|
R_TRY(reader.ReadProgramInfo());
|
||||||
|
|
||||||
|
/* Open a host location resolver. */
|
||||||
|
lr::LocationResolver host_resolver;
|
||||||
|
R_TRY(lr::OpenLocationResolver(std::addressof(host_resolver), ncm::StorageId::Host));
|
||||||
|
|
||||||
|
/* Get the content path. */
|
||||||
|
lr::Path content_path;
|
||||||
|
R_TRY(reader.GetContentPath(std::addressof(content_path), ncm::ContentType::Program, reader.GetProgramIndex()));
|
||||||
|
|
||||||
|
/* Erase the program redirection. */
|
||||||
|
R_TRY(host_resolver.EraseProgramRedirection(reader.GetProgramId()));
|
||||||
|
|
||||||
|
/* Redirect the program path to point to the new path. */
|
||||||
|
host_resolver.RedirectProgramPath(content_path, reader.GetProgramId());
|
||||||
|
|
||||||
|
/* Launch the program. */
|
||||||
|
return pgl::srv::LaunchProgram(out, ncm::ProgramLocation::Make(reader.GetProgramId(), ncm::StorageId::Host), pm_flags, pgl::LaunchFlags_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *package_path) {
|
||||||
|
/* Read the package. */
|
||||||
|
HostPackageReader reader;
|
||||||
|
R_TRY(reader.Initialize(package_path, HostPackageMountName));
|
||||||
|
|
||||||
|
/* Read the program info. */
|
||||||
|
R_TRY(reader.ReadProgramInfo());
|
||||||
|
|
||||||
|
/* Get the content meta info. */
|
||||||
|
*out = {
|
||||||
|
.id = reader.GetProgramId().value,
|
||||||
|
.version = reader.GetProgramVersion(),
|
||||||
|
.content_type = ncm::ContentType::Program,
|
||||||
|
.id_offset = reader.GetProgramIndex(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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::pgl::srv {
|
||||||
|
|
||||||
|
Result LaunchProgramFromHost(os::ProcessId *out, const char *content_path, u32 pm_flags);
|
||||||
|
Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *content_path);
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
#include "pgl_srv_shell.hpp"
|
#include "pgl_srv_shell.hpp"
|
||||||
#include "pgl_srv_shell_event_observer.hpp"
|
#include "pgl_srv_shell_event_observer.hpp"
|
||||||
|
#include "pgl_srv_shell_host_utils.hpp"
|
||||||
|
|
||||||
namespace ams::pgl::srv {
|
namespace ams::pgl::srv {
|
||||||
|
|
||||||
|
@ -28,11 +29,11 @@ namespace ams::pgl::srv {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ShellInterface::LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags) {
|
Result ShellInterface::LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags) {
|
||||||
/* TODO */
|
return pgl::srv::LaunchProgramFromHost(out.GetPointer(), reinterpret_cast<const char *>(content_path.GetPointer()), pm_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ShellInterface::GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path) {
|
Result ShellInterface::GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path) {
|
||||||
/* TODO */
|
return pgl::srv::GetHostContentMetaInfo(out.GetPointer(), reinterpret_cast<const char *>(content_path.GetPointer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ShellInterface::GetApplicationProcessId(ams::sf::Out<os::ProcessId> out) {
|
Result ShellInterface::GetApplicationProcessId(ams::sf::Out<os::ProcessId> out) {
|
||||||
|
|
Loading…
Reference in a new issue