Merge pull request #2546 from DarkLordZach/kips
loader, file_sys: Add support for parsing and loading KIP (Kernel Internal Process) files
This commit is contained in:
commit
e2f7933b3f
|
@ -45,6 +45,8 @@ add_library(core STATIC
|
||||||
file_sys/fsmitm_romfsbuild.h
|
file_sys/fsmitm_romfsbuild.h
|
||||||
file_sys/ips_layer.cpp
|
file_sys/ips_layer.cpp
|
||||||
file_sys/ips_layer.h
|
file_sys/ips_layer.h
|
||||||
|
file_sys/kernel_executable.cpp
|
||||||
|
file_sys/kernel_executable.h
|
||||||
file_sys/mode.h
|
file_sys/mode.h
|
||||||
file_sys/nca_metadata.cpp
|
file_sys/nca_metadata.cpp
|
||||||
file_sys/nca_metadata.h
|
file_sys/nca_metadata.h
|
||||||
|
@ -440,6 +442,8 @@ add_library(core STATIC
|
||||||
loader/deconstructed_rom_directory.h
|
loader/deconstructed_rom_directory.h
|
||||||
loader/elf.cpp
|
loader/elf.cpp
|
||||||
loader/elf.h
|
loader/elf.h
|
||||||
|
loader/kip.cpp
|
||||||
|
loader/kip.h
|
||||||
loader/loader.cpp
|
loader/loader.cpp
|
||||||
loader/loader.h
|
loader/loader.h
|
||||||
loader/nax.cpp
|
loader/nax.cpp
|
||||||
|
|
|
@ -22,8 +22,10 @@
|
||||||
#include "core/crypto/key_manager.h"
|
#include "core/crypto/key_manager.h"
|
||||||
#include "core/crypto/partition_data_manager.h"
|
#include "core/crypto/partition_data_manager.h"
|
||||||
#include "core/crypto/xts_encryption_layer.h"
|
#include "core/crypto/xts_encryption_layer.h"
|
||||||
|
#include "core/file_sys/kernel_executable.h"
|
||||||
#include "core/file_sys/vfs.h"
|
#include "core/file_sys/vfs.h"
|
||||||
#include "core/file_sys/vfs_offset.h"
|
#include "core/file_sys/vfs_offset.h"
|
||||||
|
#include "core/file_sys/vfs_vector.h"
|
||||||
|
|
||||||
using namespace Common;
|
using namespace Common;
|
||||||
|
|
||||||
|
@ -45,36 +47,6 @@ struct Package2Header {
|
||||||
};
|
};
|
||||||
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
|
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
|
||||||
|
|
||||||
struct INIHeader {
|
|
||||||
u32_le magic;
|
|
||||||
u32_le size;
|
|
||||||
u32_le process_count;
|
|
||||||
INSERT_PADDING_BYTES(4);
|
|
||||||
};
|
|
||||||
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
|
|
||||||
|
|
||||||
struct SectionHeader {
|
|
||||||
u32_le offset;
|
|
||||||
u32_le size_decompressed;
|
|
||||||
u32_le size_compressed;
|
|
||||||
u32_le attribute;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
|
|
||||||
|
|
||||||
struct KIPHeader {
|
|
||||||
u32_le magic;
|
|
||||||
std::array<char, 12> name;
|
|
||||||
u64_le title_id;
|
|
||||||
u32_le category;
|
|
||||||
u8 priority;
|
|
||||||
u8 core;
|
|
||||||
INSERT_PADDING_BYTES(1);
|
|
||||||
u8 flags;
|
|
||||||
std::array<SectionHeader, 6> sections;
|
|
||||||
std::array<u32, 0x20> capabilities;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
|
|
||||||
|
|
||||||
const std::array<SHA256Hash, 0x10> source_hashes{
|
const std::array<SHA256Hash, 0x10> source_hashes{
|
||||||
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
|
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
|
||||||
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
|
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
|
||||||
|
@ -170,65 +142,6 @@ const std::array<SHA256Hash, 0x20> master_key_hashes{
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
|
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
|
|
||||||
const auto data_size = in.size() - 0xC;
|
|
||||||
|
|
||||||
u32 compressed_size{};
|
|
||||||
u32 init_index{};
|
|
||||||
u32 additional_size{};
|
|
||||||
std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
|
|
||||||
std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
|
|
||||||
std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
|
|
||||||
|
|
||||||
std::vector<u8> out(in.size() + additional_size);
|
|
||||||
|
|
||||||
if (compressed_size == in.size())
|
|
||||||
std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
|
|
||||||
else
|
|
||||||
std::memcpy(out.data(), in.data(), compressed_size);
|
|
||||||
|
|
||||||
auto index = in.size() - init_index;
|
|
||||||
auto out_index = out.size();
|
|
||||||
|
|
||||||
while (out_index > 0) {
|
|
||||||
--index;
|
|
||||||
auto control = in[index];
|
|
||||||
for (size_t i = 0; i < 8; ++i) {
|
|
||||||
if ((control & 0x80) > 0) {
|
|
||||||
ASSERT(index >= 2);
|
|
||||||
index -= 2;
|
|
||||||
u64 segment_offset = in[index] | in[index + 1] << 8;
|
|
||||||
u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
|
|
||||||
segment_offset &= 0xFFF;
|
|
||||||
segment_offset += 3;
|
|
||||||
|
|
||||||
if (out_index < segment_size)
|
|
||||||
segment_size = out_index;
|
|
||||||
|
|
||||||
ASSERT(out_index >= segment_size);
|
|
||||||
|
|
||||||
out_index -= segment_size;
|
|
||||||
|
|
||||||
for (size_t j = 0; j < segment_size; ++j) {
|
|
||||||
ASSERT(out_index + j + segment_offset < out.size());
|
|
||||||
out[out_index + j] = out[out_index + j + segment_offset];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ASSERT(out_index >= 1);
|
|
||||||
--out_index;
|
|
||||||
--index;
|
|
||||||
out[out_index] = in[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
control <<= 1;
|
|
||||||
if (out_index == 0)
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u8 CalculateMaxKeyblobSourceHash() {
|
static u8 CalculateMaxKeyblobSourceHash() {
|
||||||
for (s8 i = 0x1F; i >= 0; --i) {
|
for (s8 i = 0x1F; i >= 0; --i) {
|
||||||
if (keyblob_source_hashes[i] != SHA256Hash{})
|
if (keyblob_source_hashes[i] != SHA256Hash{})
|
||||||
|
@ -478,37 +391,22 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa
|
||||||
cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
|
cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
|
||||||
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
|
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
|
||||||
|
|
||||||
INIHeader ini;
|
const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c);
|
||||||
std::memcpy(&ini, c.data(), sizeof(INIHeader));
|
const FileSys::INI ini{ini_file};
|
||||||
if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
|
if (ini.GetStatus() != Loader::ResultStatus::Success)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
u64 offset = sizeof(INIHeader);
|
for (const auto& kip : ini.GetKIPs()) {
|
||||||
for (size_t i = 0; i < ini.process_count; ++i) {
|
if (kip.GetStatus() != Loader::ResultStatus::Success)
|
||||||
KIPHeader kip;
|
|
||||||
std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
|
|
||||||
if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto name =
|
if (kip.GetName() != "FS" && kip.GetName() != "spl") {
|
||||||
Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
|
|
||||||
|
|
||||||
if (name != "FS" && name != "spl") {
|
|
||||||
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
|
|
||||||
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u64 initial_offset = sizeof(KIPHeader) + offset;
|
const auto& text = kip.GetTextSection();
|
||||||
const auto text_begin = c.cbegin() + initial_offset;
|
const auto& rodata = kip.GetRODataSection();
|
||||||
const auto text_end = text_begin + kip.sections[0].size_compressed;
|
const auto& data = kip.GetDataSection();
|
||||||
const std::vector<u8> text = DecompressBLZ({text_begin, text_end});
|
|
||||||
|
|
||||||
const auto rodata_end = text_end + kip.sections[1].size_compressed;
|
|
||||||
const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end});
|
|
||||||
|
|
||||||
const auto data_end = rodata_end + kip.sections[2].size_compressed;
|
|
||||||
const std::vector<u8> data = DecompressBLZ({rodata_end, data_end});
|
|
||||||
|
|
||||||
std::vector<u8> out;
|
std::vector<u8> out;
|
||||||
out.reserve(text.size() + rodata.size() + data.size());
|
out.reserve(text.size() + rodata.size() + data.size());
|
||||||
|
@ -516,12 +414,9 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa
|
||||||
out.insert(out.end(), rodata.begin(), rodata.end());
|
out.insert(out.end(), rodata.begin(), rodata.end());
|
||||||
out.insert(out.end(), data.begin(), data.end());
|
out.insert(out.end(), data.begin(), data.end());
|
||||||
|
|
||||||
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
|
if (kip.GetName() == "FS")
|
||||||
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
|
|
||||||
|
|
||||||
if (name == "FS")
|
|
||||||
package2_fs[static_cast<size_t>(type)] = std::move(out);
|
package2_fs[static_cast<size_t>(type)] = std::move(out);
|
||||||
else if (name == "spl")
|
else if (kip.GetName() == "spl")
|
||||||
package2_spl[static_cast<size_t>(type)] = std::move(out);
|
package2_spl[static_cast<size_t>(type)] = std::move(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
// Copyright 2019 yuzu emulator team
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "core/file_sys/kernel_executable.h"
|
||||||
|
#include "core/file_sys/vfs_offset.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
constexpr u32 INI_MAX_KIPS = 0x50;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool DecompressBLZ(std::vector<u8>& data) {
|
||||||
|
if (data.size() < 0xC)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const auto data_size = data.size() - 0xC;
|
||||||
|
|
||||||
|
u32 compressed_size{};
|
||||||
|
u32 init_index{};
|
||||||
|
u32 additional_size{};
|
||||||
|
std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32));
|
||||||
|
std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32));
|
||||||
|
std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32));
|
||||||
|
|
||||||
|
const auto start_offset = data.size() - compressed_size;
|
||||||
|
data.resize(compressed_size + additional_size + start_offset);
|
||||||
|
|
||||||
|
std::size_t index = compressed_size - init_index;
|
||||||
|
std::size_t out_index = compressed_size + additional_size;
|
||||||
|
|
||||||
|
while (out_index > 0) {
|
||||||
|
--index;
|
||||||
|
auto control = data[index + start_offset];
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
if (((control << i) & 0x80) > 0) {
|
||||||
|
if (index < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
index -= 2;
|
||||||
|
std::size_t segment_offset =
|
||||||
|
data[index + start_offset] | data[index + start_offset + 1] << 8;
|
||||||
|
std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3;
|
||||||
|
segment_offset &= 0xFFF;
|
||||||
|
segment_offset += 3;
|
||||||
|
|
||||||
|
if (out_index < segment_size)
|
||||||
|
segment_size = out_index;
|
||||||
|
|
||||||
|
if (out_index < segment_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_index -= segment_size;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < segment_size; ++j) {
|
||||||
|
if (out_index + j + segment_offset + start_offset >= data.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data[out_index + j + start_offset] =
|
||||||
|
data[out_index + j + segment_offset + start_offset];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (out_index < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
--out_index;
|
||||||
|
--index;
|
||||||
|
data[out_index + start_offset] = data[index + start_offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_index == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
|
||||||
|
if (file == nullptr) {
|
||||||
|
status = Loader::ResultStatus::ErrorNullFile;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) {
|
||||||
|
status = Loader::ResultStatus::ErrorBadKIPHeader;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) {
|
||||||
|
status = Loader::ResultStatus::ErrorBadKIPHeader;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 offset = sizeof(KIPHeader);
|
||||||
|
for (std::size_t i = 0; i < header.sections.size(); ++i) {
|
||||||
|
auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset);
|
||||||
|
offset += header.sections[i].compressed_size;
|
||||||
|
|
||||||
|
if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) {
|
||||||
|
decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size);
|
||||||
|
} else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) {
|
||||||
|
decompressed_sections[i] = std::move(compressed);
|
||||||
|
} else {
|
||||||
|
decompressed_sections[i] = compressed;
|
||||||
|
if (!DecompressBLZ(decompressed_sections[i])) {
|
||||||
|
status = Loader::ResultStatus::ErrorBLZDecompressionFailed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader::ResultStatus KIP::GetStatus() const {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string KIP::GetName() const {
|
||||||
|
return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 KIP::GetTitleID() const {
|
||||||
|
return header.title_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> KIP::GetSectionDecompressed(u8 index) const {
|
||||||
|
return decompressed_sections[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KIP::Is64Bit() const {
|
||||||
|
return (header.flags & 0x8) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KIP::Is39BitAddressSpace() const {
|
||||||
|
return (header.flags & 0x10) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KIP::IsService() const {
|
||||||
|
return (header.flags & 0x20) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> KIP::GetKernelCapabilities() const {
|
||||||
|
return std::vector<u32>(header.capabilities.begin(), header.capabilities.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 KIP::GetMainThreadPriority() const {
|
||||||
|
return header.main_thread_priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetMainThreadStackSize() const {
|
||||||
|
return header.sections[1].attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetMainThreadCpuCore() const {
|
||||||
|
return header.default_core;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<u8>& KIP::GetTextSection() const {
|
||||||
|
return decompressed_sections[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<u8>& KIP::GetRODataSection() const {
|
||||||
|
return decompressed_sections[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<u8>& KIP::GetDataSection() const {
|
||||||
|
return decompressed_sections[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetTextOffset() const {
|
||||||
|
return header.sections[0].offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetRODataOffset() const {
|
||||||
|
return header.sections[1].offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetDataOffset() const {
|
||||||
|
return header.sections[2].offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetBSSSize() const {
|
||||||
|
return header.sections[3].decompressed_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KIP::GetBSSOffset() const {
|
||||||
|
return header.sections[3].offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
|
||||||
|
if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) {
|
||||||
|
status = Loader::ResultStatus::ErrorBadINIHeader;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) {
|
||||||
|
status = Loader::ResultStatus::ErrorBadINIHeader;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.kip_count > INI_MAX_KIPS) {
|
||||||
|
status = Loader::ResultStatus::ErrorINITooManyKIPs;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 offset = sizeof(INIHeader);
|
||||||
|
for (std::size_t i = 0; i < header.kip_count; ++i) {
|
||||||
|
const auto kip_file =
|
||||||
|
std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset);
|
||||||
|
KIP kip(kip_file);
|
||||||
|
if (kip.GetStatus() == Loader::ResultStatus::Success) {
|
||||||
|
kips.push_back(std::move(kip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader::ResultStatus INI::GetStatus() const {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<KIP>& INI::GetKIPs() const {
|
||||||
|
return kips;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FileSys
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright 2019 yuzu emulator team
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
#include "core/file_sys/vfs_types.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
struct KIPSectionHeader {
|
||||||
|
u32_le offset;
|
||||||
|
u32_le decompressed_size;
|
||||||
|
u32_le compressed_size;
|
||||||
|
u32_le attribute;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size.");
|
||||||
|
|
||||||
|
struct KIPHeader {
|
||||||
|
u32_le magic;
|
||||||
|
std::array<char, 0xC> name;
|
||||||
|
u64_le title_id;
|
||||||
|
u32_le process_category;
|
||||||
|
u8 main_thread_priority;
|
||||||
|
u8 default_core;
|
||||||
|
INSERT_PADDING_BYTES(1);
|
||||||
|
u8 flags;
|
||||||
|
std::array<KIPSectionHeader, 6> sections;
|
||||||
|
std::array<u32, 0x20> capabilities;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
|
||||||
|
|
||||||
|
struct INIHeader {
|
||||||
|
u32_le magic;
|
||||||
|
u32_le size;
|
||||||
|
u32_le kip_count;
|
||||||
|
INSERT_PADDING_BYTES(0x4);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
|
||||||
|
|
||||||
|
// Kernel Internal Process
|
||||||
|
class KIP {
|
||||||
|
public:
|
||||||
|
explicit KIP(const VirtualFile& file);
|
||||||
|
|
||||||
|
Loader::ResultStatus GetStatus() const;
|
||||||
|
|
||||||
|
std::string GetName() const;
|
||||||
|
u64 GetTitleID() const;
|
||||||
|
std::vector<u8> GetSectionDecompressed(u8 index) const;
|
||||||
|
|
||||||
|
// Executable Flags
|
||||||
|
bool Is64Bit() const;
|
||||||
|
bool Is39BitAddressSpace() const;
|
||||||
|
bool IsService() const;
|
||||||
|
|
||||||
|
std::vector<u32> GetKernelCapabilities() const;
|
||||||
|
|
||||||
|
s32 GetMainThreadPriority() const;
|
||||||
|
u32 GetMainThreadStackSize() const;
|
||||||
|
u32 GetMainThreadCpuCore() const;
|
||||||
|
|
||||||
|
const std::vector<u8>& GetTextSection() const;
|
||||||
|
const std::vector<u8>& GetRODataSection() const;
|
||||||
|
const std::vector<u8>& GetDataSection() const;
|
||||||
|
|
||||||
|
u32 GetTextOffset() const;
|
||||||
|
u32 GetRODataOffset() const;
|
||||||
|
u32 GetDataOffset() const;
|
||||||
|
|
||||||
|
u32 GetBSSSize() const;
|
||||||
|
u32 GetBSSOffset() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Loader::ResultStatus status;
|
||||||
|
|
||||||
|
KIPHeader header{};
|
||||||
|
std::array<std::vector<u8>, 6> decompressed_sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
class INI {
|
||||||
|
public:
|
||||||
|
explicit INI(const VirtualFile& file);
|
||||||
|
|
||||||
|
Loader::ResultStatus GetStatus() const;
|
||||||
|
|
||||||
|
const std::vector<KIP>& GetKIPs() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Loader::ResultStatus status;
|
||||||
|
|
||||||
|
INIHeader header{};
|
||||||
|
std::vector<KIP> kips;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FileSys
|
|
@ -51,6 +51,21 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
||||||
return Loader::ResultStatus::Success;
|
return Loader::ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space,
|
||||||
|
u8 main_thread_prio, u8 main_thread_core,
|
||||||
|
u32 main_thread_stack_size, u64 title_id,
|
||||||
|
u64 filesystem_permissions,
|
||||||
|
KernelCapabilityDescriptors capabilities) {
|
||||||
|
npdm_header.has_64_bit_instructions.Assign(is_64_bit);
|
||||||
|
npdm_header.address_space_type.Assign(address_space);
|
||||||
|
npdm_header.main_thread_priority = main_thread_prio;
|
||||||
|
npdm_header.main_thread_cpu = main_thread_core;
|
||||||
|
npdm_header.main_stack_size = main_thread_stack_size;
|
||||||
|
aci_header.title_id = title_id;
|
||||||
|
aci_file_access.permissions = filesystem_permissions;
|
||||||
|
aci_kernel_capabilities = std ::move(capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
bool ProgramMetadata::Is64BitProgram() const {
|
bool ProgramMetadata::Is64BitProgram() const {
|
||||||
return npdm_header.has_64_bit_instructions;
|
return npdm_header.has_64_bit_instructions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ public:
|
||||||
|
|
||||||
Loader::ResultStatus Load(VirtualFile file);
|
Loader::ResultStatus Load(VirtualFile file);
|
||||||
|
|
||||||
|
// Load from parameters instead of NPDM file, used for KIP
|
||||||
|
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, u8 main_thread_prio,
|
||||||
|
u8 main_thread_core, u32 main_thread_stack_size, u64 title_id,
|
||||||
|
u64 filesystem_permissions, KernelCapabilityDescriptors capabilities);
|
||||||
|
|
||||||
bool Is64BitProgram() const;
|
bool Is64BitProgram() const;
|
||||||
ProgramAddressSpaceType GetAddressSpaceType() const;
|
ProgramAddressSpaceType GetAddressSpaceType() const;
|
||||||
u8 GetMainThreadPriority() const;
|
u8 GetMainThreadPriority() const;
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2019 yuzu emulator team
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/file_sys/kernel_executable.h"
|
||||||
|
#include "core/file_sys/program_metadata.h"
|
||||||
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#include "core/hle/kernel/code_set.h"
|
||||||
|
#include "core/hle/kernel/process.h"
|
||||||
|
#include "core/loader/kip.h"
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr u32 PageAlignSize(u32 size) {
|
||||||
|
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
AppLoader_KIP::AppLoader_KIP(FileSys::VirtualFile file_)
|
||||||
|
: AppLoader(std::move(file_)), kip(std::make_unique<FileSys::KIP>(file)) {}
|
||||||
|
|
||||||
|
AppLoader_KIP::~AppLoader_KIP() = default;
|
||||||
|
|
||||||
|
FileType AppLoader_KIP::IdentifyType(const FileSys::VirtualFile& file) {
|
||||||
|
u32_le magic{};
|
||||||
|
if (file->GetSize() < sizeof(u32) || file->ReadObject(&magic) != sizeof(u32)) {
|
||||||
|
return FileType::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magic == Common::MakeMagic('K', 'I', 'P', '1')) {
|
||||||
|
return FileType::KIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FileType::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileType AppLoader_KIP::GetFileType() const {
|
||||||
|
return (kip != nullptr && kip->GetStatus() == ResultStatus::Success) ? FileType::KIP
|
||||||
|
: FileType::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
|
||||||
|
if (is_loaded) {
|
||||||
|
return {ResultStatus::ErrorAlreadyLoaded, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kip == nullptr) {
|
||||||
|
return {ResultStatus::ErrorNullFile, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kip->GetStatus() != ResultStatus::Success) {
|
||||||
|
return {kip->GetStatus(), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto get_kip_address_space_type = [](const auto& kip) {
|
||||||
|
return kip.Is64Bit()
|
||||||
|
? (kip.Is39BitAddressSpace() ? FileSys::ProgramAddressSpaceType::Is39Bit
|
||||||
|
: FileSys::ProgramAddressSpaceType::Is36Bit)
|
||||||
|
: FileSys::ProgramAddressSpaceType::Is32Bit;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto address_space = get_kip_address_space_type(*kip);
|
||||||
|
|
||||||
|
FileSys::ProgramMetadata metadata;
|
||||||
|
metadata.LoadManual(kip->Is64Bit(), address_space, kip->GetMainThreadPriority(),
|
||||||
|
kip->GetMainThreadCpuCore(), kip->GetMainThreadStackSize(),
|
||||||
|
kip->GetTitleID(), 0xFFFFFFFFFFFFFFFF, kip->GetKernelCapabilities());
|
||||||
|
|
||||||
|
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
|
||||||
|
Kernel::CodeSet codeset;
|
||||||
|
std::vector<u8> program_image;
|
||||||
|
|
||||||
|
const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment,
|
||||||
|
const std::vector<u8>& data, u32 offset) {
|
||||||
|
segment.addr = offset;
|
||||||
|
segment.offset = offset;
|
||||||
|
segment.size = PageAlignSize(static_cast<u32>(data.size()));
|
||||||
|
program_image.resize(offset);
|
||||||
|
program_image.insert(program_image.end(), data.begin(), data.end());
|
||||||
|
};
|
||||||
|
|
||||||
|
load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset());
|
||||||
|
load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset());
|
||||||
|
load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset());
|
||||||
|
|
||||||
|
program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
|
||||||
|
codeset.DataSegment().size += kip->GetBSSSize();
|
||||||
|
|
||||||
|
GDBStub::RegisterModule(kip->GetName(), base_address, base_address + program_image.size());
|
||||||
|
|
||||||
|
codeset.memory = std::move(program_image);
|
||||||
|
process.LoadModule(std::move(codeset), base_address);
|
||||||
|
|
||||||
|
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address);
|
||||||
|
|
||||||
|
is_loaded = true;
|
||||||
|
return {ResultStatus::Success,
|
||||||
|
LoadParameters{kip->GetMainThreadPriority(), kip->GetMainThreadStackSize()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Loader
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2019 yuzu emulator team
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
class KIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
|
||||||
|
class AppLoader_KIP final : public AppLoader {
|
||||||
|
public:
|
||||||
|
explicit AppLoader_KIP(FileSys::VirtualFile file);
|
||||||
|
~AppLoader_KIP() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the file
|
||||||
|
* @param file std::shared_ptr<VfsFile> open file
|
||||||
|
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||||
|
*/
|
||||||
|
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||||
|
|
||||||
|
FileType GetFileType() const override;
|
||||||
|
|
||||||
|
LoadResult Load(Kernel::Process& process) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<FileSys::KIP> kip;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Loader
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/loader/deconstructed_rom_directory.h"
|
#include "core/loader/deconstructed_rom_directory.h"
|
||||||
#include "core/loader/elf.h"
|
#include "core/loader/elf.h"
|
||||||
|
#include "core/loader/kip.h"
|
||||||
#include "core/loader/nax.h"
|
#include "core/loader/nax.h"
|
||||||
#include "core/loader/nca.h"
|
#include "core/loader/nca.h"
|
||||||
#include "core/loader/nro.h"
|
#include "core/loader/nro.h"
|
||||||
|
@ -36,6 +37,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||||
CHECK_TYPE(XCI)
|
CHECK_TYPE(XCI)
|
||||||
CHECK_TYPE(NAX)
|
CHECK_TYPE(NAX)
|
||||||
CHECK_TYPE(NSP)
|
CHECK_TYPE(NSP)
|
||||||
|
CHECK_TYPE(KIP)
|
||||||
|
|
||||||
#undef CHECK_TYPE
|
#undef CHECK_TYPE
|
||||||
|
|
||||||
|
@ -63,6 +65,8 @@ FileType GuessFromFilename(const std::string& name) {
|
||||||
return FileType::XCI;
|
return FileType::XCI;
|
||||||
if (extension == "nsp")
|
if (extension == "nsp")
|
||||||
return FileType::NSP;
|
return FileType::NSP;
|
||||||
|
if (extension == "kip")
|
||||||
|
return FileType::KIP;
|
||||||
|
|
||||||
return FileType::Unknown;
|
return FileType::Unknown;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +87,8 @@ std::string GetFileTypeString(FileType type) {
|
||||||
return "NAX";
|
return "NAX";
|
||||||
case FileType::NSP:
|
case FileType::NSP:
|
||||||
return "NSP";
|
return "NSP";
|
||||||
|
case FileType::KIP:
|
||||||
|
return "KIP";
|
||||||
case FileType::DeconstructedRomDirectory:
|
case FileType::DeconstructedRomDirectory:
|
||||||
return "Directory";
|
return "Directory";
|
||||||
case FileType::Error:
|
case FileType::Error:
|
||||||
|
@ -93,7 +99,7 @@ std::string GetFileTypeString(FileType type) {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr std::array<const char*, 62> RESULT_MESSAGES{
|
constexpr std::array<const char*, 66> RESULT_MESSAGES{
|
||||||
"The operation completed successfully.",
|
"The operation completed successfully.",
|
||||||
"The loader requested to load is already loaded.",
|
"The loader requested to load is already loaded.",
|
||||||
"The operation is not implemented.",
|
"The operation is not implemented.",
|
||||||
|
@ -156,6 +162,10 @@ constexpr std::array<const char*, 62> RESULT_MESSAGES{
|
||||||
"The BKTR-type NCA has a bad Subsection bucket.",
|
"The BKTR-type NCA has a bad Subsection bucket.",
|
||||||
"The BKTR-type NCA is missing the base RomFS.",
|
"The BKTR-type NCA is missing the base RomFS.",
|
||||||
"The NSP or XCI does not contain an update in addition to the base game.",
|
"The NSP or XCI does not contain an update in addition to the base game.",
|
||||||
|
"The KIP file has a bad header.",
|
||||||
|
"The KIP BLZ decompression of the section failed unexpectedly.",
|
||||||
|
"The INI file has a bad header.",
|
||||||
|
"The INI file contains more than the maximum allowable number of KIP files.",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
||||||
|
@ -205,6 +215,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
|
||||||
case FileType::NSP:
|
case FileType::NSP:
|
||||||
return std::make_unique<AppLoader_NSP>(std::move(file));
|
return std::make_unique<AppLoader_NSP>(std::move(file));
|
||||||
|
|
||||||
|
// NX KIP (Kernel Internal Process) file format
|
||||||
|
case FileType::KIP:
|
||||||
|
return std::make_unique<AppLoader_KIP>(std::move(file));
|
||||||
|
|
||||||
// NX deconstructed ROM directory.
|
// NX deconstructed ROM directory.
|
||||||
case FileType::DeconstructedRomDirectory:
|
case FileType::DeconstructedRomDirectory:
|
||||||
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
|
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
|
||||||
|
|
|
@ -37,6 +37,7 @@ enum class FileType {
|
||||||
NSP,
|
NSP,
|
||||||
XCI,
|
XCI,
|
||||||
NAX,
|
NAX,
|
||||||
|
KIP,
|
||||||
DeconstructedRomDirectory,
|
DeconstructedRomDirectory,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,6 +125,10 @@ enum class ResultStatus : u16 {
|
||||||
ErrorBadSubsectionBuckets,
|
ErrorBadSubsectionBuckets,
|
||||||
ErrorMissingBKTRBaseRomFS,
|
ErrorMissingBKTRBaseRomFS,
|
||||||
ErrorNoPackedUpdate,
|
ErrorNoPackedUpdate,
|
||||||
|
ErrorBadKIPHeader,
|
||||||
|
ErrorBLZDecompressionFailed,
|
||||||
|
ErrorBadINIHeader,
|
||||||
|
ErrorINITooManyKIPs,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
||||||
|
|
|
@ -468,8 +468,7 @@ void GameList::LoadInterfaceLayout() {
|
||||||
|
|
||||||
const QStringList GameList::supported_file_extensions = {
|
const QStringList GameList::supported_file_extensions = {
|
||||||
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
|
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
|
||||||
QStringLiteral("xci"), QStringLiteral("nsp"),
|
QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
|
||||||
};
|
|
||||||
|
|
||||||
void GameList::RefreshGameDirectory() {
|
void GameList::RefreshGameDirectory() {
|
||||||
if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
|
if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
|
||||||
|
|
Loading…
Reference in New Issue