Add XCI file format support

This commit is contained in:
sspacelynx 2021-02-27 23:11:09 +01:00 committed by ◱ Mark
parent 29e13fbee0
commit 666df1eb43
7 changed files with 224 additions and 1 deletions

View File

@ -72,6 +72,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/loader/nro.cpp
${source_DIR}/skyline/loader/nso.cpp
${source_DIR}/skyline/loader/nca.cpp
${source_DIR}/skyline/loader/xci.cpp
${source_DIR}/skyline/loader/nsp.cpp
${source_DIR}/skyline/vfs/os_filesystem.cpp
${source_DIR}/skyline/vfs/partition_filesystem.cpp

View File

@ -7,6 +7,7 @@
#include "skyline/loader/nro.h"
#include "skyline/loader/nso.h"
#include "skyline/loader/nca.h"
#include "skyline/loader/xci.h"
#include "skyline/loader/nsp.h"
#include "skyline/jvm.h"
@ -31,6 +32,9 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn
case skyline::loader::RomFormat::NCA:
loader = std::make_unique<skyline::loader::NcaLoader>(backing, keyStore);
break;
case skyline::loader::RomFormat::XCI:
loader = std::make_unique<skyline::loader::XciLoader>(backing, keyStore);
break;
case skyline::loader::RomFormat::NSP:
loader = std::make_unique<skyline::loader::NspLoader>(backing, keyStore);
break;

View File

@ -45,7 +45,7 @@ namespace skyline::loader {
return std::vector<u8>();
auto root{controlRomFs->OpenDirectory("", {false, true})};
std::shared_ptr<vfs::Backing> icon;
std::shared_ptr<vfs::Backing> icon{};
// Use the first icon file available
for (const auto &entry : root->Read()) {

View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <common.h>
#include <kernel/types/KProcess.h>
#include <vfs/region_backing.h>
#include "nca.h"
#include "xci.h"
namespace skyline::loader {
XciLoader::XciLoader(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<crypto::KeyStore> &keyStore) {
header = backing->Read<GamecardHeader>();
if (header.magic != util::MakeMagic<u32>("HEAD"))
throw exception("Invalid XCI file");
xci = std::make_shared<vfs::PartitionFileSystem>(std::make_shared<vfs::RegionBacking>(backing, header.hfs0PartitionOffset, header.hfs0HeaderSize * sizeof(u32)));
auto root{xci->OpenDirectory("", {false, true})};
for (const auto &entry : root->Read()) {
auto entryDir{std::make_shared<vfs::PartitionFileSystem>(xci->OpenFile(entry.name))};
if (entry.name == "secure")
secure = entryDir;
else if (entry.name == "normal")
normal = entryDir;
else if (entry.name == "update")
update = entryDir;
else if (entry.name == "logo")
logo = entryDir;
}
if (secure) {
root = secure->OpenDirectory("", {false, true});
for (const auto &entry : root->Read()) {
if (entry.name.substr(entry.name.find_last_of('.') + 1) != "nca")
continue;
try {
auto nca{vfs::NCA(secure->OpenFile(entry.name), keyStore)};
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)
controlNca = std::move(nca);
} catch (const loader_exception &e) {
throw loader_exception(e.error);
} catch (const std::exception &e) {
continue;
}
}
} else {
throw exception("Corrupted secure partition");
}
if (!programNca || !controlNca)
throw exception("Incomplete XCI file");
romFs = programNca->romFs;
controlRomFs = std::make_shared<vfs::RomFileSystem>(controlNca->romFs);
nacp.emplace(controlRomFs->OpenFile("control.nacp"));
}
void *XciLoader::LoadProcessData(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state) {
process->npdm = vfs::NPDM(programNca->exeFs->OpenFile("main.npdm"), state);
return NcaLoader::LoadExeFs(this, programNca->exeFs, process, state);
}
std::vector<u8> XciLoader::GetIcon() {
if (romFs == nullptr)
return std::vector<u8>();
auto root{controlRomFs->OpenDirectory("", {false, true})};
std::shared_ptr<vfs::Backing> icon{};
// Use the first icon file available
for (const auto &entry : root->Read()) {
if (entry.name.rfind("icon") == 0) {
icon = controlRomFs->OpenFile(entry.name);
break;
}
}
if (icon == nullptr)
return std::vector<u8>();
std::vector<u8> buffer(icon->size);
icon->Read(buffer);
return buffer;
}
}

View File

@ -0,0 +1,124 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <crypto/key_store.h>
#include <vfs/partition_filesystem.h>
#include <vfs/rom_filesystem.h>
#include <vfs/nca.h>
#include "loader.h"
namespace skyline::loader {
/**
* @brief The XciLoader class abstracts access to an XCI file through the Loader interface
* @url https://switchbrew.org/wiki/XCI
*/
class XciLoader : public Loader {
private:
enum class GamecardSize : u8 {
Size1GB = 0xFA,
Size2GB = 0xF8,
Size4GB = 0xF0,
Size8GB = 0xE0,
Size16GB = 0xE1,
Size32GB = 0xE2
};
enum class GamecardFlags : u8 {
AutoBoot = 0,
HistoryErase = 1,
RepairTool = 2,
DifferentRegionCupToTerraDevice = 3,
DifferentRegionCupToGlobalDevice = 4
};
/**
* @brief The encryption type used for gamecard verification
*/
enum class SecurityMode : u8 {
T1 = 0x01,
T2 = 0x02
};
enum class FirmwareVersion : u64 {
Development = 0x00,
Retail = 0x01,
Retail400 = 0x02, //!< [4.0.0+] Retail
Retail1100 = 0x04 //!< [11.0.0+] Retail
};
/**
* @brief The speed at which the gamecard is accessed
*/
enum class AccessControl : u32 {
ClockRate25Mhz = 0x00A10011,
ClockRate50Mhz = 0x00A10010
};
/**
* @brief [9.0.0+] The region of Switch HW the gamecard is compatible with
*/
enum class CompatType : u8 {
Global = 0x00, //!< Normal
China = 0x01 //!< Terra
};
struct GamecardInfo {
FirmwareVersion firmwareVersion;
AccessControl accessControl; //!< The speed at which the gamecard is accessed
u32 readTimeWait1; //!< Read Time Wait1, always 0x1388
u32 readTimeWait2; //!< Read Time Wait2, always 0
u32 writeTimeWait1; //!< Write Time Wait1, always 0
u32 writeTimeWait2; //!< Write Time Wait2, always 0
u32 firmwareMode;
u32 cupVersion;
CompatType compatType;
u8 _pad0_[0x3];
u64 updatePartitionHash;
u64 cupId; //!< CUP ID, always 0x0100000000000816, which is the title-listing data archive's title ID
u8 _pad1_[0x38];
};
static_assert(sizeof(GamecardInfo) == 0x70);
struct GamecardHeader {
u8 signature[0x100]; //!< RSA-2048 PKCS #1 signature over the header
u32 magic; //!< The magic of the gamecard format: 'HEAD'
u32 secureAreaStartAddress; //!< Secure Area Start Address in media units
u32 backupAreaStartAddress; //!< Backup Area Start Address, always 0xFFFFFFFF
u8 titleKeyDecKekIndex; //!< TitleKeyDec Index (high nibble) and KEK Index (low nibble)
GamecardSize size;
u8 version; //!< Gamecard header version
GamecardFlags flags; //!< GameCardAttribute
u64 packageId; //!< The package ID, used for challengeresponse authentication
u64 validDataEndAddress; //!< Valid Data End Address in media units
std::array<u8, 0x10> infoIv; //!< Gamecard Info IV (reversed)
u64 hfs0PartitionOffset; //!< The HFS0 header partition offset
u64 hfs0HeaderSize;
std::array<u8, 0x20> hfs0HeaderSha256; //!< SHA-256 hash of the HFS0 Header
std::array<u8, 0x20> initialDataSha256; //!< SHA-256 hash of the Initial Data
SecurityMode securityMode;
u32 t1KeyIndex; //!< T1 Key Index, always 2
u32 keyIndex; //!< Key Index, always 0
u32 normalAreaEndAddress; //!< Normal Area End Address in media units
GamecardInfo gamecardInfo; //!< Gamecard Info (AES-128-CBC encrypted)
} header{};
static_assert(sizeof(GamecardHeader) == 0x200);
std::shared_ptr<vfs::PartitionFileSystem> xci; //!< A shared pointer to XCI HFS0 header partition
std::shared_ptr<vfs::PartitionFileSystem> secure; //!< A shared pointer to the secure HFS0 partition
std::shared_ptr<vfs::PartitionFileSystem> update; //!< A shared pointer to the update HFS0 partition
std::shared_ptr<vfs::PartitionFileSystem> normal; //!< A shared pointer to the normal HFS0 partition
std::shared_ptr<vfs::PartitionFileSystem> logo; //!< A shared pointer to the logo HFS0 partition
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
public:
XciLoader(const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<crypto::KeyStore> &keyStore);
std::vector<u8> GetIcon() override;
void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> &process, const DeviceState &state) override;
};
}

View File

@ -9,6 +9,7 @@
#include "loader/nso.h"
#include "loader/nca.h"
#include "loader/nsp.h"
#include "loader/xci.h"
#include "os.h"
namespace skyline::kernel {
@ -28,6 +29,8 @@ namespace skyline::kernel {
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.");
}

View File

@ -139,6 +139,7 @@ class MainActivity : AppCompatActivity() {
addEntries("nro", RomFormat.NRO, searchLocation, romElements)
addEntries("nso", RomFormat.NSO, searchLocation, romElements)
addEntries("nca", RomFormat.NCA, searchLocation, romElements)
addEntries("xci", RomFormat.XCI, searchLocation, romElements)
addEntries("nsp", RomFormat.NSP, searchLocation, romElements)
runOnUiThread {