mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 03:31:49 +01:00
Add XCI file format support
This commit is contained in:
parent
29e13fbee0
commit
666df1eb43
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
90
app/src/main/cpp/skyline/loader/xci.cpp
Normal file
90
app/src/main/cpp/skyline/loader/xci.cpp
Normal 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;
|
||||
}
|
||||
}
|
124
app/src/main/cpp/skyline/loader/xci.h
Normal file
124
app/src/main/cpp/skyline/loader/xci.h
Normal 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 challenge–response 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;
|
||||
};
|
||||
}
|
@ -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.");
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user