Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VFS Part 1: DLC and Update support #160

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ add_library(skyline SHARED
${source_DIR}/skyline/vfs/npdm.cpp
${source_DIR}/skyline/vfs/nca.cpp
${source_DIR}/skyline/vfs/ticket.cpp
${source_DIR}/skyline/vfs/cnmt.cpp
${source_DIR}/skyline/vfs/bktr.cpp
${source_DIR}/skyline/vfs/patch_manager.cpp
${source_DIR}/skyline/services/serviceman.cpp
${source_DIR}/skyline/services/base_service.cpp
${source_DIR}/skyline/services/sm/IUserInterface.cpp
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/cpp/emu_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ extern "C" JNIEXPORT void Java_org_stratoemu_strato_EmulationActivity_executeApp
jstring romUriJstring,
jint romType,
jint romFd,
jintArray dlcFds,
jint updateFd,
jobject settingsInstance,
jstring publicAppFilesPathJstring,
jstring privateAppFilesPathJstring,
Expand All @@ -75,6 +77,12 @@ extern "C" JNIEXPORT void Java_org_stratoemu_strato_EmulationActivity_executeApp

auto jvmManager{std::make_shared<skyline::JvmManager>(env, instance)};

jsize dlcArrSize = dlcFds != nullptr ? env->GetArrayLength(dlcFds) : 0;
std::vector<int> dlcFdsVector(dlcArrSize);

if (dlcArrSize > 0)
env->GetIntArrayRegion(dlcFds, 0, dlcArrSize, &dlcFdsVector[0]);

std::shared_ptr<skyline::Settings> settings{std::make_shared<skyline::AndroidSettings>(env, settingsInstance)};

skyline::JniString publicAppFilesPath(env, publicAppFilesPathJstring);
Expand Down Expand Up @@ -114,7 +122,7 @@ extern "C" JNIEXPORT void Java_org_stratoemu_strato_EmulationActivity_executeApp

LOGDNF("Launching ROM {}", skyline::JniString(env, romUriJstring));

os->Execute(romFd, static_cast<skyline::loader::RomFormat>(romType));
os->Execute(romFd, dlcFdsVector, updateFd, static_cast<skyline::loader::RomFormat>(romType));
} catch (std::exception &e) {
LOGENF("An uncaught exception has occurred: {}", e.what());
} catch (const skyline::signal::SignalException &e) {
Expand All @@ -132,6 +140,13 @@ extern "C" JNIEXPORT void Java_org_stratoemu_strato_EmulationActivity_executeApp

skyline::AsyncLogger::Finalize(true);
close(romFd);

close(updateFd);

if (dlcArrSize > 0)
for (int i = 0; i < dlcArrSize; i++)
close(env->GetIntArrayElements(dlcFds, nullptr)[i]);

}

extern "C" JNIEXPORT jboolean Java_org_stratoemu_strato_EmulationActivity_stopEmulation(JNIEnv *, jobject, jboolean join) {
Expand Down
17 changes: 15 additions & 2 deletions app/src/main/cpp/loader_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "skyline/vfs/nca.h"
#include "skyline/vfs/os_backing.h"
#include "skyline/vfs/os_filesystem.h"
#include "skyline/vfs/cnmt.h"
#include "skyline/loader/nro.h"
#include "skyline/loader/nso.h"
#include "skyline/loader/nca.h"
Expand Down Expand Up @@ -50,9 +51,12 @@ extern "C" JNIEXPORT jint JNICALL Java_org_stratoemu_strato_loader_RomFile_popul
jclass clazz{env->GetObjectClass(thiz)};
jfieldID applicationNameField{env->GetFieldID(clazz, "applicationName", "Ljava/lang/String;")};
jfieldID applicationTitleIdField{env->GetFieldID(clazz, "applicationTitleId", "Ljava/lang/String;")};
jfieldID addOnContentBaseIdField{env->GetFieldID(clazz, "addOnContentBaseId", "Ljava/lang/String;")};
jfieldID applicationAuthorField{env->GetFieldID(clazz, "applicationAuthor", "Ljava/lang/String;")};
jfieldID rawIconField{env->GetFieldID(clazz, "rawIcon", "[B")};
jfieldID applicationVersionField{env->GetFieldID(clazz, "applicationVersion", "Ljava/lang/String;")};
jfieldID romType{env->GetFieldID(clazz, "romTypeInt", "I")};
jfieldID parentTitleId{env->GetFieldID(clazz, "parentTitleId", "Ljava/lang/String;")};

if (loader->nacp) {
auto language{skyline::language::GetApplicationLanguage(static_cast<skyline::language::SystemLanguage>(systemLanguage))};
Expand All @@ -62,6 +66,7 @@ extern "C" JNIEXPORT jint JNICALL Java_org_stratoemu_strato_loader_RomFile_popul
env->SetObjectField(thiz, applicationNameField, env->NewStringUTF(loader->nacp->GetApplicationName(language).c_str()));
env->SetObjectField(thiz, applicationVersionField, env->NewStringUTF(loader->nacp->GetApplicationVersion().c_str()));
env->SetObjectField(thiz, applicationTitleIdField, env->NewStringUTF(loader->nacp->GetSaveDataOwnerId().c_str()));
env->SetObjectField(thiz, addOnContentBaseIdField, env->NewStringUTF(loader->nacp->GetAddOnContentBaseId().c_str()));
env->SetObjectField(thiz, applicationAuthorField, env->NewStringUTF(loader->nacp->GetApplicationPublisher(language).c_str()));

auto icon{loader->GetIcon(language)};
Expand All @@ -70,6 +75,14 @@ extern "C" JNIEXPORT jint JNICALL Java_org_stratoemu_strato_loader_RomFile_popul
env->SetObjectField(thiz, rawIconField, iconByteArray);
}

if (loader->cnmt) {
auto contentMetaType{loader->cnmt->GetContentMetaType()};
env->SetIntField(thiz, romType, static_cast<skyline::u8>(contentMetaType));

if (contentMetaType != skyline::vfs::ContentMetaType::Application)
env->SetObjectField(thiz, parentTitleId, env->NewStringUTF(loader->cnmt->GetParentTitleId().c_str()));
}

return static_cast<jint>(skyline::loader::LoaderResult::Success);
}

Expand Down Expand Up @@ -98,7 +111,7 @@ extern "C" JNIEXPORT jstring Java_org_stratoemu_strato_preference_FirmwareImport
std::shared_ptr<skyline::vfs::Backing> backing{systemArchivesFileSystem->OpenFile(entry.name)};
auto nca{skyline::vfs::NCA(backing, keyStore)};

if (nca.header.programId == systemVersionProgramId && nca.romFs != nullptr) {
if (nca.header.titleId == systemVersionProgramId && nca.romFs != nullptr) {
auto controlRomFs{std::make_shared<skyline::vfs::RomFileSystem>(nca.romFs)};
auto file{controlRomFs->OpenFile("file")};
SystemVersion systemVersion;
Expand Down Expand Up @@ -165,7 +178,7 @@ extern "C" JNIEXPORT void Java_org_stratoemu_strato_preference_FirmwareImportPre
std::shared_ptr<skyline::vfs::Backing> backing{systemArchivesFileSystem->OpenFile(entry.name)};
auto nca{skyline::vfs::NCA(backing, keyStore)};

if (nca.header.programId >= firstFontProgramId && nca.header.programId <= lastFontProgramId && nca.romFs != nullptr) {
if (nca.header.titleId >= firstFontProgramId && nca.header.titleId <= lastFontProgramId && nca.romFs != nullptr) {
auto controlRomFs{std::make_shared<skyline::vfs::RomFileSystem>(nca.romFs)};

for (auto fileEntry = controlRomFs->fileMap.begin(); fileEntry != controlRomFs->fileMap.end(); fileEntry++) {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/cpp/skyline/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ namespace skyline {
std::shared_ptr<JvmManager> jvm;
std::shared_ptr<Settings> settings;
std::shared_ptr<loader::Loader> loader;
std::vector<std::shared_ptr<loader::Loader>> dlcLoaders;
std::shared_ptr<loader::Loader> updateLoader;
std::shared_ptr<nce::NCE> nce;
std::shared_ptr<kernel::type::KProcess> process{};
static thread_local inline std::shared_ptr<kernel::type::KThread> thread{}; //!< The KThread of the thread which accesses this object
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/cpp/skyline/loader/loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <linux/elf.h>
#include <vfs/nacp.h>
#include <vfs/cnmt.h>
#include <vfs/nca.h>
#include <common/signal.h>
#include "executable.h"

Expand Down Expand Up @@ -32,6 +34,9 @@ namespace skyline::loader {
MissingTitleKey,
MissingTitleKek,
MissingKeyArea,

ErrorSparseNCA,
ErrorCompressedNCA,
};

/**
Expand Down Expand Up @@ -85,6 +90,10 @@ namespace skyline::loader {
ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string &name = {}, bool dynamicallyLinked = false);

std::optional<vfs::NACP> nacp;
std::optional<vfs::CNMT> cnmt;
std::optional<vfs::NCA> programNca; //!< The main program NCA within the NSP
std::optional<vfs::NCA> controlNca; //!< The main control NCA within the NSP
std::optional<vfs::NCA> publicNca;
std::shared_ptr<vfs::Backing> romFs;

virtual ~Loader() = default;
Expand Down
28 changes: 21 additions & 7 deletions app/src/main/cpp/skyline/loader/nsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <vfs/ticket.h>
#include "nca.h"
#include "nsp.h"
#include "vfs/patch_manager.h"

namespace skyline::loader {
static void ExtractTickets(const std::shared_ptr<vfs::PartitionFileSystem>& dir, const std::shared_ptr<crypto::KeyStore> &keyStore) {
Expand Down Expand Up @@ -33,26 +34,39 @@ namespace skyline::loader {
try {
auto nca{vfs::NCA(nsp->OpenFile(entry.name), keyStore)};

if (nca.contentType == vfs::NcaContentType::Program && nca.romFs != nullptr && nca.exeFs != nullptr)
if (nca.contentType == vfs::NCAContentType::Program && nca.romFs != nullptr && nca.exeFs != nullptr)
programNca = std::move(nca);
else if (nca.contentType == vfs::NcaContentType::Control && nca.romFs != nullptr)
else if (nca.contentType == vfs::NCAContentType::Control && nca.romFs != nullptr)
controlNca = std::move(nca);
else if (nca.contentType == vfs::NCAContentType::Meta)
metaNca = std::move(nca);
else if (nca.contentType == vfs::NCAContentType::PublicData)
publicNca = std::move(nca);
} catch (const loader_exception &e) {
throw loader_exception(e.error);
} catch (const std::exception &e) {
continue;
}
}

if (!programNca || !controlNca)
throw exception("Incomplete NSP file");
if (programNca)
romFs = programNca->romFs;

romFs = programNca->romFs;
controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs);
nacp.emplace(controlRomFs->OpenFile("control.nacp"));
if (controlNca) {
controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs);
nacp.emplace(controlRomFs->OpenFile("control.nacp"));
}

if (metaNca)
cnmt = vfs::CNMT(metaNca->cnmt);
}

void *NspLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state) {
if (state.updateLoader) {
auto patchManager{std::make_shared<vfs::PatchManager>()};
programNca->exeFs = patchManager->PatchExeFS(state, programNca->exeFs);
}

process->npdm = vfs::NPDM(programNca->exeFs->OpenFile("main.npdm"));
return NcaLoader::LoadExeFs(this, programNca->exeFs, process, state);
}
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/cpp/skyline/loader/nsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ namespace skyline::loader {
private:
std::shared_ptr<vfs::PartitionFileSystem> nsp; //!< A shared pointer to the NSP's PFS0
std::shared_ptr<vfs::RomFileSystem> controlRomFs; //!< A shared pointer to the control NCA's RomFS
std::optional<vfs::NCA> programNca; //!< The main program NCA within the NSP
std::optional<vfs::NCA> controlNca; //!< The main control NCA within the NSP
std::optional<vfs::NCA> metaNca; //!< The main meta NCA within the NSP

public:
NspLoader(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<crypto::KeyStore> &keyStore);
Expand Down
20 changes: 13 additions & 7 deletions app/src/main/cpp/skyline/loader/xci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ namespace skyline::loader {
try {
auto nca{vfs::NCA(secure->OpenFile(entry.name), keyStore, true)};

if (nca.contentType == vfs::NcaContentType::Program && nca.romFs != nullptr && nca.exeFs != nullptr)
if (nca.contentType == vfs::NCAContentType::Program && nca.romFs != nullptr && nca.exeFs != nullptr)
programNca = std::move(nca);
else if (nca.contentType == vfs::NcaContentType::Control && nca.romFs != nullptr)
else if (nca.contentType == vfs::NCAContentType::Control && nca.romFs != nullptr)
controlNca = std::move(nca);
else if (nca.contentType == vfs::NCAContentType::Meta)
metaNca = std::move(nca);
} catch (const loader_exception &e) {
throw loader_exception(e.error);
} catch (const std::exception &e) {
Expand All @@ -52,12 +54,16 @@ namespace skyline::loader {
throw exception("Corrupted secure partition");
}

if (!programNca || !controlNca)
throw exception("Incomplete XCI file");
if (programNca)
romFs = programNca->romFs;

romFs = programNca->romFs;
controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs);
nacp.emplace(controlRomFs->OpenFile("control.nacp"));
if (controlNca) {
controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs);
nacp.emplace(controlRomFs->OpenFile("control.nacp"));
}

if (metaNca)
cnmt = vfs::CNMT(metaNca->cnmt);
}

void *XciLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state) {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/cpp/skyline/loader/xci.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ namespace skyline::loader {
std::shared_ptr<vfs::RomFileSystem> controlRomFs; //!< A shared pointer to the control NCA's RomFS
std::optional<vfs::NCA> programNca; //!< The main program NCA within the secure partition
std::optional<vfs::NCA> controlNca; //!< The main control NCA within the secure partition
std::optional<vfs::NCA> metaNca; //!< The main meta NCA within the secure partition

public:
XciLoader(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<crypto::KeyStore> &keyStore);
Expand Down
56 changes: 37 additions & 19 deletions app/src/main/cpp/skyline/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,18 @@ namespace skyline::kernel {
state(this, jvmManager, settings),
serviceManager(state) {}

void OS::Execute(int romFd, loader::RomFormat romType) {
void OS::Execute(int romFd, std::vector<int> dlcFds, int updateFd, loader::RomFormat romType) {
auto romFile{std::make_shared<vfs::OsBacking>(romFd)};
auto keyStore{std::make_shared<crypto::KeyStore>(privateAppFilesPath + "keys/")};
keyStore = std::make_shared<crypto::KeyStore>(privateAppFilesPath + "keys/");

state.loader = [&]() -> std::shared_ptr<loader::Loader> {
switch (romType) {
case loader::RomFormat::NRO:
return std::make_shared<loader::NroLoader>(std::move(romFile));
case loader::RomFormat::NSO:
return std::make_shared<loader::NsoLoader>(std::move(romFile));
case loader::RomFormat::NCA:
return std::make_shared<loader::NcaLoader>(std::move(romFile), std::move(keyStore));
case loader::RomFormat::NSP:
return std::make_shared<loader::NspLoader>(romFile, keyStore);
case loader::RomFormat::XCI:
return std::make_shared<loader::XciLoader>(romFile, keyStore);
default:
throw exception("Unsupported ROM extension.");
}
}();
state.loader = GetLoader(romFd, keyStore, romType);

if (updateFd > 0)
state.updateLoader = GetLoader(updateFd, keyStore, romType);

if (dlcFds.size() > 0)
for (int fd : dlcFds)
state.dlcLoaders.push_back(GetLoader(fd, keyStore, romType));

state.gpu->Initialise();

Expand All @@ -64,7 +56,15 @@ namespace skyline::kernel {
name = nacp->GetApplicationName(nacp->GetFirstSupportedTitleLanguage());
if (publisher.empty())
publisher = nacp->GetApplicationPublisher(nacp->GetFirstSupportedTitleLanguage());
LOGINF(R"(Starting "{}" ({}) v{} by "{}")", name, nacp->GetSaveDataOwnerId(), nacp->GetApplicationVersion(), publisher);

if (state.updateLoader)
LOGINF("Applied update v{}", state.updateLoader->nacp->GetApplicationVersion());

if (state.dlcLoaders.size() > 0)
for (auto &loader : state.dlcLoaders)
LOGINF("Applied DLC {}", loader->cnmt->GetTitleId());

LOGINF(R"(Starting "{}" ({}) v{} by "{}")", name, nacp->GetSaveDataOwnerId(), state.updateLoader ? state.updateLoader->nacp->GetApplicationVersion() : nacp->GetApplicationVersion(), publisher);
}

process->InitializeHeapTls();
Expand All @@ -75,4 +75,22 @@ namespace skyline::kernel {
process->Kill(true, true, true);
}
}

std::shared_ptr<loader::Loader> OS::GetLoader(int fd, std::shared_ptr<crypto::KeyStore> keyStore, loader::RomFormat romType) {
auto file{std::make_shared<vfs::OsBacking>(fd)};
switch (romType) {
case loader::RomFormat::NRO:
return std::make_shared<loader::NroLoader>(std::move(file));
case loader::RomFormat::NSO:
return std::make_shared<loader::NsoLoader>(std::move(file));
case loader::RomFormat::NCA:
return std::make_shared<loader::NcaLoader>(std::move(file), std::move(keyStore));
case loader::RomFormat::NSP:
return std::make_shared<loader::NspLoader>(file, keyStore);
case loader::RomFormat::XCI:
return std::make_shared<loader::XciLoader>(file, keyStore);
default:
throw exception("Unsupported ROM extension.");
}
}
}
8 changes: 7 additions & 1 deletion app/src/main/cpp/skyline/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma once

#include <crypto/key_store.h>
#include <common/language.h>
#include "vfs/filesystem.h"
#include "loader/loader.h"
Expand All @@ -19,6 +20,7 @@ namespace skyline::kernel {
std::string privateAppFilesPath; //!< The full path to the app's private files directory
std::string deviceTimeZone; //!< The timezone name (e.g. Europe/London)
std::shared_ptr<vfs::FileSystem> assetFileSystem; //!< A filesystem to be used for accessing emulator assets (like tzdata)
std::shared_ptr<crypto::KeyStore> keyStore;
DeviceState state;
service::ServiceManager serviceManager;

Expand All @@ -39,8 +41,12 @@ namespace skyline::kernel {
/**
* @brief Execute a particular ROM file
* @param romFd A FD to the ROM file to execute
* @param dlcFds An array of FD to the DLC files
* @param updateFd A FD to the Update file
* @param romType The type of the ROM file
*/
void Execute(int romFd, loader::RomFormat romType);
void Execute(int romFd, std::vector<int> dlcFds, int updateFd, loader::RomFormat romType);

std::shared_ptr<loader::Loader> GetLoader(int fd, std::shared_ptr<crypto::KeyStore> keyStore, loader::RomFormat romType);
};
}
Loading