mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-23 01:19:16 +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/nro.cpp
|
||||||
${source_DIR}/skyline/loader/nso.cpp
|
${source_DIR}/skyline/loader/nso.cpp
|
||||||
${source_DIR}/skyline/loader/nca.cpp
|
${source_DIR}/skyline/loader/nca.cpp
|
||||||
|
${source_DIR}/skyline/loader/xci.cpp
|
||||||
${source_DIR}/skyline/loader/nsp.cpp
|
${source_DIR}/skyline/loader/nsp.cpp
|
||||||
${source_DIR}/skyline/vfs/os_filesystem.cpp
|
${source_DIR}/skyline/vfs/os_filesystem.cpp
|
||||||
${source_DIR}/skyline/vfs/partition_filesystem.cpp
|
${source_DIR}/skyline/vfs/partition_filesystem.cpp
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "skyline/loader/nro.h"
|
#include "skyline/loader/nro.h"
|
||||||
#include "skyline/loader/nso.h"
|
#include "skyline/loader/nso.h"
|
||||||
#include "skyline/loader/nca.h"
|
#include "skyline/loader/nca.h"
|
||||||
|
#include "skyline/loader/xci.h"
|
||||||
#include "skyline/loader/nsp.h"
|
#include "skyline/loader/nsp.h"
|
||||||
#include "skyline/jvm.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:
|
case skyline::loader::RomFormat::NCA:
|
||||||
loader = std::make_unique<skyline::loader::NcaLoader>(backing, keyStore);
|
loader = std::make_unique<skyline::loader::NcaLoader>(backing, keyStore);
|
||||||
break;
|
break;
|
||||||
|
case skyline::loader::RomFormat::XCI:
|
||||||
|
loader = std::make_unique<skyline::loader::XciLoader>(backing, keyStore);
|
||||||
|
break;
|
||||||
case skyline::loader::RomFormat::NSP:
|
case skyline::loader::RomFormat::NSP:
|
||||||
loader = std::make_unique<skyline::loader::NspLoader>(backing, keyStore);
|
loader = std::make_unique<skyline::loader::NspLoader>(backing, keyStore);
|
||||||
break;
|
break;
|
||||||
|
@ -45,7 +45,7 @@ namespace skyline::loader {
|
|||||||
return std::vector<u8>();
|
return std::vector<u8>();
|
||||||
|
|
||||||
auto root{controlRomFs->OpenDirectory("", {false, true})};
|
auto root{controlRomFs->OpenDirectory("", {false, true})};
|
||||||
std::shared_ptr<vfs::Backing> icon;
|
std::shared_ptr<vfs::Backing> icon{};
|
||||||
|
|
||||||
// Use the first icon file available
|
// Use the first icon file available
|
||||||
for (const auto &entry : root->Read()) {
|
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/nso.h"
|
||||||
#include "loader/nca.h"
|
#include "loader/nca.h"
|
||||||
#include "loader/nsp.h"
|
#include "loader/nsp.h"
|
||||||
|
#include "loader/xci.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
namespace skyline::kernel {
|
namespace skyline::kernel {
|
||||||
@ -28,6 +29,8 @@ namespace skyline::kernel {
|
|||||||
return std::make_shared<loader::NcaLoader>(std::move(romFile), std::move(keyStore));
|
return std::make_shared<loader::NcaLoader>(std::move(romFile), std::move(keyStore));
|
||||||
case loader::RomFormat::NSP:
|
case loader::RomFormat::NSP:
|
||||||
return std::make_shared<loader::NspLoader>(romFile, keyStore);
|
return std::make_shared<loader::NspLoader>(romFile, keyStore);
|
||||||
|
case loader::RomFormat::XCI:
|
||||||
|
return std::make_shared<loader::XciLoader>(romFile, keyStore);
|
||||||
default:
|
default:
|
||||||
throw exception("Unsupported ROM extension.");
|
throw exception("Unsupported ROM extension.");
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
addEntries("nro", RomFormat.NRO, searchLocation, romElements)
|
addEntries("nro", RomFormat.NRO, searchLocation, romElements)
|
||||||
addEntries("nso", RomFormat.NSO, searchLocation, romElements)
|
addEntries("nso", RomFormat.NSO, searchLocation, romElements)
|
||||||
addEntries("nca", RomFormat.NCA, searchLocation, romElements)
|
addEntries("nca", RomFormat.NCA, searchLocation, romElements)
|
||||||
|
addEntries("xci", RomFormat.XCI, searchLocation, romElements)
|
||||||
addEntries("nsp", RomFormat.NSP, searchLocation, romElements)
|
addEntries("nsp", RomFormat.NSP, searchLocation, romElements)
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
Loading…
Reference in New Issue
Block a user