mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +02:00
Initial commit
This commit is contained in:
@@ -0,0 +1,646 @@
|
||||
#include "loader/elf.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace Loader {
|
||||
|
||||
static std::unique_ptr<SelfHeader> LoadSelf(Common::File& f) {
|
||||
if (f.Remaining() < sizeof(SelfHeader)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto self = std::make_unique<SelfHeader>();
|
||||
|
||||
f.Read(self.get(), sizeof(SelfHeader));
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static std::unique_ptr<SelfSegment[]> LoadSelfSegments(Common::File& f, uint16_t num) {
|
||||
auto segs = std::make_unique<SelfSegment[]>(num);
|
||||
|
||||
f.Read(segs.get(), sizeof(SelfSegment) * num);
|
||||
|
||||
return segs;
|
||||
}
|
||||
|
||||
static std::unique_ptr<Elf64_Ehdr> LoadEhdr64(Common::File& f) {
|
||||
if (f.Remaining() < sizeof(Elf64_Ehdr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ehdr = std::make_unique<Elf64_Ehdr>();
|
||||
|
||||
f.Read(ehdr.get(), sizeof(Elf64_Ehdr));
|
||||
|
||||
return ehdr;
|
||||
}
|
||||
|
||||
static void SaveEhdr64(Common::File& f, const Elf64_Ehdr* ehdr) {
|
||||
EXIT_IF(ehdr == nullptr);
|
||||
|
||||
uint32_t bytes_written = 0;
|
||||
|
||||
f.Write(ehdr, sizeof(Elf64_Ehdr), &bytes_written);
|
||||
|
||||
EXIT_IF(bytes_written == 0);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Elf64_Phdr[]> LoadPhdr64(Common::File& f, uint64_t offset, Elf64_Half num) {
|
||||
auto phdr = std::make_unique<Elf64_Phdr[]>(num);
|
||||
|
||||
f.Seek(offset);
|
||||
f.Read(phdr.get(), sizeof(Elf64_Phdr) * num);
|
||||
|
||||
return phdr;
|
||||
}
|
||||
|
||||
static void SavePhdr64(Common::File& f, uint64_t offset, Elf64_Half num, const Elf64_Phdr* phdr) {
|
||||
EXIT_IF(phdr == nullptr);
|
||||
|
||||
uint32_t bytes_written = 0;
|
||||
|
||||
f.Seek(offset);
|
||||
f.Write(phdr, sizeof(Elf64_Phdr) * num, &bytes_written);
|
||||
|
||||
EXIT_IF(bytes_written == 0);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Elf64_Shdr[]> LoadShdr64(Common::File& f, uint64_t offset, Elf64_Half num) {
|
||||
if (num == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto shdr = std::make_unique<Elf64_Shdr[]>(num);
|
||||
|
||||
f.Seek(offset);
|
||||
f.Read(shdr.get(), sizeof(Elf64_Shdr) * num);
|
||||
|
||||
return shdr;
|
||||
}
|
||||
|
||||
static void SaveShdr64(Common::File& f, uint64_t offset, Elf64_Half num, const Elf64_Shdr* shdr) {
|
||||
if (num == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXIT_IF(shdr == nullptr);
|
||||
|
||||
uint32_t bytes_written = 0;
|
||||
|
||||
f.Seek(offset);
|
||||
f.Write(shdr, sizeof(Elf64_Shdr) * num, &bytes_written);
|
||||
|
||||
EXIT_IF(bytes_written == 0);
|
||||
}
|
||||
|
||||
static std::unique_ptr<uint8_t[]> LoadDynamic64(Elf64* f, uint64_t offset, uint64_t size) {
|
||||
auto dynamic_data = std::make_unique<uint8_t[]>(size);
|
||||
|
||||
// f.Seek(offset);
|
||||
// f.Read(dynamic_data, size);
|
||||
|
||||
f->LoadSegment(reinterpret_cast<uint64_t>(dynamic_data.get()), offset, size);
|
||||
|
||||
return dynamic_data;
|
||||
}
|
||||
|
||||
static std::unique_ptr<char[]> LoadStrTable(Common::File& f, uint64_t offset, uint32_t size) {
|
||||
if (size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto str_table = std::make_unique<char[]>(size);
|
||||
f.Seek(offset);
|
||||
f.Read(str_table.get(), size);
|
||||
return str_table;
|
||||
}
|
||||
|
||||
static void DbgPrintEhdr64(Elf64_Ehdr* ehdr, Common::File& f) {
|
||||
f.Printf("ehdr->e_ident = ");
|
||||
for (auto i: ehdr->e_ident) {
|
||||
f.Printf("%02x", i);
|
||||
}
|
||||
f.Printf("\n");
|
||||
|
||||
f.Printf("ehdr->e_type = 0x%04" PRIx16 "\n", ehdr->e_type);
|
||||
f.Printf("ehdr->e_machine = 0x%04" PRIx16 "\n", ehdr->e_machine);
|
||||
f.Printf("ehdr->e_version = 0x%08" PRIx32 "\n", ehdr->e_version);
|
||||
|
||||
f.Printf("ehdr->e_entry = 0x%016" PRIx64 "\n", ehdr->e_entry);
|
||||
f.Printf("ehdr->e_phoff = 0x%016" PRIx64 "\n", ehdr->e_phoff);
|
||||
f.Printf("ehdr->e_shoff = 0x%016" PRIx64 "\n", ehdr->e_shoff);
|
||||
f.Printf("ehdr->e_flags = 0x%08" PRIx32 "\n", ehdr->e_flags);
|
||||
f.Printf("ehdr->e_ehsize = 0x%04" PRIx16 "\n", ehdr->e_ehsize);
|
||||
f.Printf("ehdr->e_phentsize = 0x%04" PRIx16 "\n", ehdr->e_phentsize);
|
||||
f.Printf("ehdr->e_phnum = %" PRIu16 "\n", ehdr->e_phnum);
|
||||
f.Printf("ehdr->e_shentsize = 0x%04" PRIx16 "\n", ehdr->e_shentsize);
|
||||
f.Printf("ehdr->e_shnum = %" PRIu16 "\n", ehdr->e_shnum);
|
||||
f.Printf("ehdr->e_shstrndx = %" PRIu16 "\n", ehdr->e_shstrndx);
|
||||
}
|
||||
|
||||
static void DbgPrintPhdr64(Elf64_Phdr* phdr, Common::File& f) {
|
||||
f.Printf("phdr->p_type = 0x%08" PRIx32 "\n", phdr->p_type);
|
||||
f.Printf("phdr->p_flags = 0x%08" PRIx32 "\n", phdr->p_flags);
|
||||
f.Printf("phdr->p_offset = 0x%016" PRIx64 "\n", phdr->p_offset);
|
||||
f.Printf("phdr->p_vaddr = 0x%016" PRIx64 "\n", phdr->p_vaddr);
|
||||
f.Printf("phdr->p_paddr = 0x%016" PRIx64 "\n", phdr->p_paddr);
|
||||
f.Printf("phdr->p_filesz = 0x%016" PRIx64 "\n", phdr->p_filesz);
|
||||
f.Printf("phdr->p_memsz = 0x%016" PRIx64 "\n", phdr->p_memsz);
|
||||
f.Printf("phdr->p_align = 0x%016" PRIx64 "\n", phdr->p_align);
|
||||
}
|
||||
|
||||
static void DbgPrintShdr64(Elf64_Shdr* shdr, Common::File& f) {
|
||||
f.Printf("shdr->sh_name = %d\n", shdr->sh_name);
|
||||
f.Printf("shdr->sh_type = 0x%08" PRIx32 "\n", shdr->sh_type);
|
||||
f.Printf("shdr->sh_flags = 0x%016" PRIx64 "\n", shdr->sh_flags);
|
||||
f.Printf("shdr->sh_addr = 0x%016" PRIx64 "\n", shdr->sh_addr);
|
||||
f.Printf("shdr->sh_offset = 0x%016" PRIx64 "\n", shdr->sh_offset);
|
||||
f.Printf("shdr->sh_size = 0x%016" PRIx64 "\n", shdr->sh_size);
|
||||
f.Printf("shdr->sh_link = %" PRId32 "\n", shdr->sh_link);
|
||||
f.Printf("shdr->sh_info = 0x%08" PRIx32 "\n", shdr->sh_info);
|
||||
f.Printf("shdr->sh_addralign = 0x%016" PRIx64 "\n", shdr->sh_addralign);
|
||||
f.Printf("shdr->sh_entsize = 0x%016" PRIx64 "\n", shdr->sh_entsize);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define DBG_NAME(tag) \
|
||||
case tag: name = #tag; break;
|
||||
|
||||
static void DbgPrintDynamic64(const Elf64_Dyn* dyn, Common::File& f) {
|
||||
const char* name = "Unknown";
|
||||
switch (dyn->d_tag) {
|
||||
DBG_NAME(DT_OS_HASH)
|
||||
DBG_NAME(DT_HASH)
|
||||
DBG_NAME(DT_OS_STRTAB)
|
||||
DBG_NAME(DT_OS_STRSZ)
|
||||
DBG_NAME(DT_STRTAB)
|
||||
DBG_NAME(DT_STRSZ)
|
||||
DBG_NAME(DT_OS_SYMTAB)
|
||||
DBG_NAME(DT_SYMTAB)
|
||||
DBG_NAME(DT_OS_HASHSZ)
|
||||
DBG_NAME(DT_OS_SYMTABSZ)
|
||||
DBG_NAME(DT_INIT)
|
||||
DBG_NAME(DT_FINI)
|
||||
DBG_NAME(DT_OS_PLTGOT)
|
||||
DBG_NAME(DT_PLTGOT)
|
||||
DBG_NAME(DT_OS_JMPREL)
|
||||
DBG_NAME(DT_JMPREL)
|
||||
DBG_NAME(DT_OS_PLTRELSZ)
|
||||
DBG_NAME(DT_PLTRELSZ)
|
||||
DBG_NAME(DT_OS_PLTREL)
|
||||
DBG_NAME(DT_PLTREL)
|
||||
DBG_NAME(DT_OS_RELA)
|
||||
DBG_NAME(DT_RELA)
|
||||
DBG_NAME(DT_OS_RELASZ)
|
||||
DBG_NAME(DT_RELASZ)
|
||||
DBG_NAME(DT_OS_RELAENT)
|
||||
DBG_NAME(DT_RELAENT)
|
||||
DBG_NAME(DT_INIT_ARRAY)
|
||||
DBG_NAME(DT_INIT_ARRAYSZ)
|
||||
DBG_NAME(DT_FINI_ARRAY)
|
||||
DBG_NAME(DT_FINI_ARRAYSZ)
|
||||
DBG_NAME(DT_PREINIT_ARRAY)
|
||||
DBG_NAME(DT_PREINIT_ARRAYSZ)
|
||||
DBG_NAME(DT_OS_SYMENT)
|
||||
DBG_NAME(DT_SYMENT)
|
||||
DBG_NAME(DT_DEBUG)
|
||||
DBG_NAME(DT_TEXTREL)
|
||||
DBG_NAME(DT_FLAGS)
|
||||
DBG_NAME(DT_NEEDED)
|
||||
DBG_NAME(DT_OS_NEEDED_MODULE)
|
||||
DBG_NAME(DT_OS_NEEDED_MODULE_1)
|
||||
DBG_NAME(DT_OS_IMPORT_LIB)
|
||||
DBG_NAME(DT_OS_IMPORT_LIB_1)
|
||||
DBG_NAME(DT_OS_IMPORT_LIB_ATTR)
|
||||
DBG_NAME(DT_OS_FINGERPRINT)
|
||||
DBG_NAME(DT_OS_ORIGINAL_FILENAME)
|
||||
DBG_NAME(DT_OS_ORIGINAL_FILENAME_1)
|
||||
DBG_NAME(DT_OS_MODULE_INFO)
|
||||
DBG_NAME(DT_OS_MODULE_INFO_1)
|
||||
DBG_NAME(DT_OS_MODULE_ATTR)
|
||||
DBG_NAME(DT_SONAME)
|
||||
DBG_NAME(DT_OS_EXPORT_LIB)
|
||||
DBG_NAME(DT_OS_EXPORT_LIB_1)
|
||||
DBG_NAME(DT_OS_EXPORT_LIB_ATTR)
|
||||
DBG_NAME(DT_RELACOUNT)
|
||||
DBG_NAME(DT_NULL)
|
||||
}
|
||||
f.Printf("d_tag = 0x%016" PRIx64 ", d_val = 0x%016" PRIx64 ", name = %s\n", dyn->d_tag,
|
||||
dyn->d_un.d_val, name);
|
||||
}
|
||||
|
||||
Elf64::~Elf64() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Elf64::LoadSegment(uint64_t vaddr, uint64_t file_offset, uint64_t size) {
|
||||
EXIT_IF(m_f == nullptr);
|
||||
|
||||
if (m_self != nullptr) {
|
||||
EXIT_IF(m_self_segments == nullptr);
|
||||
EXIT_IF(m_phdr == nullptr);
|
||||
|
||||
for (uint16_t i = 0; i < m_self->segments_num; i++) {
|
||||
const auto& seg = m_self_segments[i];
|
||||
if ((seg.type & 0x800u) != 0) {
|
||||
auto phdr_id = ((seg.type >> 20u) & 0xFFFu);
|
||||
|
||||
const auto& phdr = m_phdr[phdr_id];
|
||||
|
||||
if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
|
||||
EXIT_NOT_IMPLEMENTED(seg.decompressed_size != phdr.p_filesz);
|
||||
EXIT_NOT_IMPLEMENTED(seg.compressed_size != seg.decompressed_size);
|
||||
|
||||
auto offset = file_offset - phdr.p_offset;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(offset + size > seg.decompressed_size);
|
||||
|
||||
m_f->Seek(offset + seg.offset);
|
||||
m_f->Read(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_f->Size() - m_self->file_size == size) {
|
||||
m_f->Seek(m_self->file_size);
|
||||
m_f->Read(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
EXIT("missing self segment\n");
|
||||
} else {
|
||||
m_f->Seek(file_offset);
|
||||
m_f->Read(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), size);
|
||||
}
|
||||
}
|
||||
|
||||
const Elf64_Dyn* Elf64::GetDynValue(Elf64_Sxword tag) const {
|
||||
for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) {
|
||||
if (dyn->d_tag == tag) {
|
||||
return dyn;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const Elf64_Dyn*> Elf64::GetDynList(Elf64_Sxword tag) const {
|
||||
std::vector<const Elf64_Dyn*> ret;
|
||||
for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) {
|
||||
if (dyn->d_tag == tag) {
|
||||
ret.push_back(dyn);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Elf64::IsShared() const {
|
||||
return (m_ehdr->e_type == ET_DYNAMIC);
|
||||
}
|
||||
|
||||
bool Elf64::IsNextGen() const {
|
||||
return (m_ehdr->e_ident[EI_ABIVERSION] == 2);
|
||||
}
|
||||
|
||||
const char* Elf64::GetSectionName(int index) const {
|
||||
if (m_ehdr == nullptr || m_shdr == nullptr || m_str_table == nullptr || index < 0 ||
|
||||
index >= m_ehdr->e_shnum) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto name_offset = m_shdr[index].sh_name;
|
||||
if (name_offset >= m_str_table_size) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto remaining = m_str_table_size - name_offset;
|
||||
if (std::memchr(m_str_table.get() + name_offset, '\0', remaining) == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_str_table.get() + name_offset;
|
||||
}
|
||||
|
||||
void Elf64::Clear() {
|
||||
if (m_f != nullptr) {
|
||||
m_f->Close();
|
||||
}
|
||||
|
||||
m_f.reset();
|
||||
m_self.reset();
|
||||
m_ehdr.reset();
|
||||
m_self_segments.reset();
|
||||
m_phdr.reset();
|
||||
m_shdr.reset();
|
||||
m_str_table.reset();
|
||||
m_str_table_size = 0;
|
||||
m_dynamic.reset();
|
||||
m_dynamic_data.reset();
|
||||
}
|
||||
|
||||
void Elf64::DbgDump(const std::string& folder) {
|
||||
auto folder_str = Common::FixDirectorySlash(folder);
|
||||
|
||||
Common::File::CreateDirectories(folder_str);
|
||||
|
||||
for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) {
|
||||
if (m_phdr[i].p_filesz == 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char str[512];
|
||||
int s = snprintf(str, 512, "phdr_%03d", i);
|
||||
EXIT_NOT_IMPLEMENTED(s >= 512);
|
||||
|
||||
Common::File fout;
|
||||
fout.Create(folder_str + str);
|
||||
|
||||
auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_phdr[i].p_filesz));
|
||||
|
||||
// m_f->Seek(m_phdr[i].p_offset);
|
||||
// m_f->Read(buf, static_cast<uint32_t>(m_phdr[i].p_filesz));
|
||||
|
||||
LoadSegment(reinterpret_cast<uint64_t>(buf.get()), m_phdr[i].p_offset, m_phdr[i].p_filesz);
|
||||
|
||||
fout.Write(buf.get(), static_cast<uint32_t>(m_phdr[i].p_filesz));
|
||||
|
||||
fout.Close();
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) {
|
||||
if (m_shdr[i].sh_size == 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char str[512];
|
||||
int s = snprintf(str, 512, "shdr_%03d", i);
|
||||
EXIT_NOT_IMPLEMENTED(s >= 512);
|
||||
|
||||
Common::File fout;
|
||||
fout.Create(folder_str + str);
|
||||
|
||||
auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_shdr[i].sh_size));
|
||||
|
||||
m_f->Seek(m_shdr[i].sh_offset);
|
||||
m_f->Read(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size));
|
||||
fout.Write(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size));
|
||||
|
||||
fout.Close();
|
||||
}
|
||||
|
||||
Common::File fout;
|
||||
|
||||
fout.Create(folder_str + "ehdr.txt");
|
||||
DbgPrintEhdr64(m_ehdr.get(), fout);
|
||||
fout.Close();
|
||||
|
||||
fout.Create(folder_str + "phdr.txt");
|
||||
for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) {
|
||||
fout.Printf("--- phdr [%d] ---\n", i);
|
||||
DbgPrintPhdr64(m_phdr.get() + i, fout);
|
||||
}
|
||||
fout.Close();
|
||||
|
||||
fout.Create(folder_str + "shdr.txt");
|
||||
for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) {
|
||||
const char* section_name = GetSectionName(i);
|
||||
fout.Printf("--- shdr [%d] %s ---\n", i, section_name != nullptr ? section_name : "");
|
||||
DbgPrintShdr64(m_shdr.get() + i, fout);
|
||||
}
|
||||
fout.Close();
|
||||
|
||||
fout.Create(folder_str + "dynamic.txt");
|
||||
for (const auto* dyn = GetDynamic(); dyn->d_tag != DT_NULL; dyn++) {
|
||||
DbgPrintDynamic64(dyn, fout);
|
||||
}
|
||||
fout.Close();
|
||||
}
|
||||
|
||||
uint64_t Elf64::GetEntry() {
|
||||
return m_ehdr->e_entry;
|
||||
}
|
||||
|
||||
bool Elf64::IsSelf() const {
|
||||
if (m_f == nullptr || m_f->IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_self == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool known_magic = (m_self->ident[0] == 0x4f && m_self->ident[1] == 0x15 &&
|
||||
m_self->ident[2] == 0x3d && m_self->ident[3] == 0x1d) ||
|
||||
(m_self->ident[0] == 0x54 && m_self->ident[1] == 0x14 &&
|
||||
m_self->ident[2] == 0xf5 && m_self->ident[3] == 0xee);
|
||||
if (!known_magic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool known_ident_tail =
|
||||
(m_self->ident[4] == 0x00 && m_self->ident[5] == 0x01 && m_self->ident[6] == 0x01 &&
|
||||
m_self->ident[7] == 0x12 && m_self->ident[8] == 0x01 && m_self->ident[9] == 0x01 &&
|
||||
m_self->ident[10] == 0x00 && m_self->ident[11] == 0x00 && m_self->unknown == 0x22) ||
|
||||
(m_self->ident[4] == 0x10 && m_self->ident[5] == 0x01 && m_self->ident[6] == 0x01 &&
|
||||
m_self->ident[7] == 0x12 && m_self->ident[8] == 0x01 && m_self->ident[9] == 0x01 &&
|
||||
m_self->ident[10] == 0x00 && m_self->ident[11] == 0x10 && m_self->unknown == 0x32);
|
||||
|
||||
if (!known_ident_tail) {
|
||||
LOGF("Unknown SELF file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Elf64::IsValid() const {
|
||||
if (m_f == nullptr || m_f->IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_ident[EI_MAG0] != '\x7f' || m_ehdr->e_ident[EI_MAG1] != 'E' ||
|
||||
m_ehdr->e_ident[EI_MAG2] != 'L' || m_ehdr->e_ident[EI_MAG3] != 'F') {
|
||||
LOGF("Not an ELF file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
|
||||
LOGF("ehdr->e_ident[EI_CLASS] (0x%x) != ELFCLASS64\n", m_ehdr->e_ident[EI_CLASS]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
|
||||
LOGF("ehdr->e_ident[EI_DATA] (0x%x) != ELFDATA2LSB\n", m_ehdr->e_ident[EI_DATA]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
|
||||
LOGF("ehdr->e_ident[EI_VERSION] != EV_CURRENT\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_ident[EI_OSABI] != ELFOSABI_FREEBSD) {
|
||||
LOGF("ehdr->e_ident[EI_OSABI] (0x%x) != ELFOSABI_FREEBSD\n", m_ehdr->e_ident[EI_OSABI]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_ident[EI_ABIVERSION] != 0 && m_ehdr->e_ident[EI_ABIVERSION] != 2) {
|
||||
LOGF("ehdr->e_ident[EI_ABIVERSION] (0x%x) != (0 or 2)\n", m_ehdr->e_ident[EI_ABIVERSION]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_type != ET_DYNEXEC && m_ehdr->e_type != ET_DYNAMIC) {
|
||||
LOGF("ehdr->e_type (%04x) != ET_DYNEXEC && m_ehdr->e_type != ET_DYNAMIC\n", m_ehdr->e_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_machine != EM_X86_64) {
|
||||
LOGF("ehdr->e_machine (%04x) != EM_X86_64\n", m_ehdr->e_machine);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_version != EV_CURRENT) {
|
||||
LOGF("ehdr->e_version != EV_CURRENT\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_phentsize != sizeof(Elf64_Phdr)) {
|
||||
LOGF("ehdr->e_phentsize != sizeof(Elf64_Phdr)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ehdr->e_shentsize > 0 && m_ehdr->e_shentsize != sizeof(Elf64_Shdr)) {
|
||||
LOGF("ehdr->e_shentsize (%d) != sizeof(Elf64_Shdr)\n", m_ehdr->e_shentsize);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Elf64::Open(const std::filesystem::path& file_name) {
|
||||
Clear();
|
||||
|
||||
m_f = std::make_unique<Common::File>();
|
||||
m_f->Open(file_name, Common::File::Mode::Read);
|
||||
|
||||
if (m_f->IsInvalid()) {
|
||||
EXIT("Can't open %s\n", Common::PathToString(file_name).c_str());
|
||||
}
|
||||
|
||||
m_self = LoadSelf(*m_f);
|
||||
|
||||
if (!IsSelf()) {
|
||||
m_self.reset();
|
||||
m_f->Seek(0);
|
||||
} else {
|
||||
m_self_segments = LoadSelfSegments(*m_f, m_self->segments_num);
|
||||
}
|
||||
|
||||
auto ehdr_pos = m_f->Tell();
|
||||
|
||||
m_ehdr = LoadEhdr64(*m_f);
|
||||
|
||||
if (!IsValid()) {
|
||||
m_ehdr.reset();
|
||||
}
|
||||
|
||||
if (m_ehdr != nullptr /*&& m_self == nullptr*/) {
|
||||
m_phdr = LoadPhdr64(*m_f, ehdr_pos + m_ehdr->e_phoff, m_ehdr->e_phnum);
|
||||
if (m_self == nullptr) {
|
||||
m_shdr = LoadShdr64(*m_f, ehdr_pos + m_ehdr->e_shoff, m_ehdr->e_shnum);
|
||||
|
||||
if (m_shdr != nullptr) {
|
||||
if (m_ehdr->e_shstrndx < m_ehdr->e_shnum) {
|
||||
m_str_table_size = static_cast<uint32_t>(m_shdr[m_ehdr->e_shstrndx].sh_size);
|
||||
m_str_table =
|
||||
LoadStrTable(*m_f, m_shdr[m_ehdr->e_shstrndx].sh_offset, m_str_table_size);
|
||||
}
|
||||
}
|
||||
} else if (m_ehdr->e_shnum != 0) {
|
||||
LOGF("SELF: skipping ELF section table: shoff=0x%016" PRIx64 ", shnum=%" PRIu16 "\n",
|
||||
m_ehdr->e_shoff, m_ehdr->e_shnum);
|
||||
}
|
||||
|
||||
for (Elf64_Half i = 0; i < m_ehdr->e_phnum; i++) {
|
||||
switch (m_phdr[i].p_type) {
|
||||
case PT_DYNAMIC:
|
||||
m_dynamic = LoadDynamic64(this, m_phdr[i].p_offset, m_phdr[i].p_filesz);
|
||||
break;
|
||||
case PT_OS_DYNLIBDATA:
|
||||
m_dynamic_data = LoadDynamic64(this, m_phdr[i].p_offset, m_phdr[i].p_filesz);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Elf64::Save(const std::filesystem::path& file_name) {
|
||||
EXIT_IF(!IsValid());
|
||||
|
||||
if (IsValid()) {
|
||||
Common::File f;
|
||||
f.Create(file_name);
|
||||
|
||||
if (f.IsInvalid()) {
|
||||
EXIT("Can't create %s\n", Common::PathToString(file_name).c_str());
|
||||
}
|
||||
|
||||
SaveEhdr64(f, m_ehdr.get());
|
||||
|
||||
SavePhdr64(f, m_ehdr->e_phoff, m_ehdr->e_phnum, m_phdr.get());
|
||||
SaveShdr64(f, m_ehdr->e_shoff, m_ehdr->e_shnum, m_shdr.get());
|
||||
|
||||
for (uint16_t i = 0; i < m_ehdr->e_phnum; i++) {
|
||||
if (m_phdr[i].p_filesz == 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_phdr[i].p_filesz));
|
||||
|
||||
LoadSegment(reinterpret_cast<uint64_t>(buf.get()), m_phdr[i].p_offset,
|
||||
m_phdr[i].p_filesz);
|
||||
|
||||
uint32_t bytes_written = 0;
|
||||
|
||||
f.Seek(m_phdr[i].p_offset);
|
||||
f.Write(buf.get(), static_cast<uint32_t>(m_phdr[i].p_filesz), &bytes_written);
|
||||
|
||||
EXIT_IF(bytes_written == 0);
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < m_ehdr->e_shnum; i++) {
|
||||
if (m_shdr[i].sh_size == 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto buf = std::make_unique<char[]>(static_cast<uint32_t>(m_shdr[i].sh_size));
|
||||
|
||||
m_f->Seek(m_shdr[i].sh_offset);
|
||||
m_f->Read(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size));
|
||||
|
||||
uint32_t bytes_written = 0;
|
||||
|
||||
f.Seek(m_shdr[i].sh_offset);
|
||||
f.Write(buf.get(), static_cast<uint32_t>(m_shdr[i].sh_size), &bytes_written);
|
||||
|
||||
EXIT_IF(bytes_written == 0);
|
||||
}
|
||||
|
||||
f.Close();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
@@ -0,0 +1,308 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_ELF_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LOADER_ELF_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
class File;
|
||||
} // namespace Common
|
||||
|
||||
namespace Loader {
|
||||
|
||||
using Elf64_Addr = uint64_t; // Unsigned program address
|
||||
using Elf64_Off = uint64_t; // Unsigned file offset
|
||||
using Elf64_Half = uint16_t; // Unsigned medium integer
|
||||
using Elf64_Word = uint32_t; // Unsigned integer
|
||||
using Elf64_Sword = int32_t; // Signed integer
|
||||
using Elf64_Xword = uint64_t; // Unsigned long integer
|
||||
using Elf64_Sxword = int64_t; // Signed long integer
|
||||
|
||||
constexpr int EI_MAG0 = 0;
|
||||
constexpr int EI_MAG1 = 1;
|
||||
constexpr int EI_MAG2 = 2;
|
||||
constexpr int EI_MAG3 = 3;
|
||||
constexpr int EI_CLASS = 4;
|
||||
constexpr int EI_DATA = 5;
|
||||
constexpr int EI_VERSION = 6;
|
||||
constexpr int EI_OSABI = 7;
|
||||
constexpr int EI_ABIVERSION = 8;
|
||||
constexpr int EI_PAD = 9;
|
||||
constexpr int EI_NIDENT = 16;
|
||||
|
||||
constexpr char ELFCLASS64 = 2; // 64-bit objects
|
||||
|
||||
constexpr char ELFDATA2LSB = 1; // Object file data structures are little-endian
|
||||
|
||||
constexpr Elf64_Half EM_X86_64 = 62; /* AMD x86-64 architecture */
|
||||
|
||||
constexpr int EV_CURRENT = 1;
|
||||
|
||||
constexpr char ELFOSABI_FREEBSD = 9; // FreeBSD operating system
|
||||
|
||||
constexpr Elf64_Half ET_DYNEXEC = 0xfe10; // Executable file
|
||||
constexpr Elf64_Half ET_DYNAMIC = 0xfe18; // Shared
|
||||
|
||||
// #define SHT_PROGBITS 1
|
||||
|
||||
constexpr Elf64_Word PT_LOAD = 1;
|
||||
constexpr Elf64_Word PT_DYNAMIC = 2;
|
||||
constexpr Elf64_Word PT_TLS = 7;
|
||||
constexpr Elf64_Word PT_OS_DYNLIBDATA = 0x61000000;
|
||||
constexpr Elf64_Word PT_OS_PROCPARAM = 0x61000001;
|
||||
constexpr Elf64_Word PT_OS_RELRO = 0x61000010;
|
||||
|
||||
constexpr Elf64_Word PF_X = 0x1; // Execute
|
||||
constexpr Elf64_Word PF_W = 0x2; // Write
|
||||
constexpr Elf64_Word PF_R = 0x4; // Read
|
||||
|
||||
constexpr Elf64_Sxword DT_DEBUG = 0x00000015;
|
||||
constexpr Elf64_Sxword DT_FINI = 0x0000000d;
|
||||
constexpr Elf64_Sxword DT_FINI_ARRAY = 0x0000001a;
|
||||
constexpr Elf64_Sxword DT_FINI_ARRAYSZ = 0x0000001c;
|
||||
constexpr Elf64_Sxword DT_FLAGS = 0x0000001e;
|
||||
constexpr Elf64_Sxword DT_INIT = 0x0000000c;
|
||||
constexpr Elf64_Sxword DT_INIT_ARRAY = 0x00000019;
|
||||
constexpr Elf64_Sxword DT_INIT_ARRAYSZ = 0x0000001b;
|
||||
constexpr Elf64_Sxword DT_NEEDED = 0x00000001;
|
||||
constexpr Elf64_Sxword DT_OS_EXPORT_LIB = 0x61000013;
|
||||
constexpr Elf64_Sxword DT_OS_EXPORT_LIB_1 = 0x61000047;
|
||||
constexpr Elf64_Sxword DT_OS_EXPORT_LIB_ATTR = 0x61000017;
|
||||
constexpr Elf64_Sxword DT_OS_FINGERPRINT = 0x61000007;
|
||||
constexpr Elf64_Sxword DT_OS_HASH = 0x61000025;
|
||||
constexpr Elf64_Sxword DT_OS_HASHSZ = 0x6100003d;
|
||||
constexpr Elf64_Sxword DT_OS_IMPORT_LIB = 0x61000015;
|
||||
constexpr Elf64_Sxword DT_OS_IMPORT_LIB_1 = 0x61000049;
|
||||
constexpr Elf64_Sxword DT_OS_IMPORT_LIB_ATTR = 0x61000019;
|
||||
constexpr Elf64_Sxword DT_OS_JMPREL = 0x61000029;
|
||||
constexpr Elf64_Sxword DT_OS_MODULE_ATTR = 0x61000011;
|
||||
constexpr Elf64_Sxword DT_OS_MODULE_INFO = 0x6100000d;
|
||||
constexpr Elf64_Sxword DT_OS_MODULE_INFO_1 = 0x61000043;
|
||||
constexpr Elf64_Sxword DT_OS_NEEDED_MODULE = 0x6100000f;
|
||||
constexpr Elf64_Sxword DT_OS_NEEDED_MODULE_1 = 0x61000045;
|
||||
constexpr Elf64_Sxword DT_OS_ORIGINAL_FILENAME = 0x61000009;
|
||||
constexpr Elf64_Sxword DT_OS_ORIGINAL_FILENAME_1 = 0x61000041;
|
||||
constexpr Elf64_Sxword DT_OS_PLTGOT = 0x61000027;
|
||||
constexpr Elf64_Sxword DT_OS_PLTREL = 0x6100002b;
|
||||
constexpr Elf64_Sxword DT_OS_PLTRELSZ = 0x6100002d;
|
||||
constexpr Elf64_Sxword DT_OS_RELA = 0x6100002f;
|
||||
constexpr Elf64_Sxword DT_OS_RELAENT = 0x61000033;
|
||||
constexpr Elf64_Sxword DT_OS_RELASZ = 0x61000031;
|
||||
constexpr Elf64_Sxword DT_OS_STRSZ = 0x61000037;
|
||||
constexpr Elf64_Sxword DT_OS_STRTAB = 0x61000035;
|
||||
constexpr Elf64_Sxword DT_OS_SYMENT = 0x6100003b;
|
||||
constexpr Elf64_Sxword DT_OS_SYMTAB = 0x61000039;
|
||||
constexpr Elf64_Sxword DT_OS_SYMTABSZ = 0x6100003f;
|
||||
constexpr Elf64_Sxword DT_PREINIT_ARRAY = 0x00000020;
|
||||
constexpr Elf64_Sxword DT_PREINIT_ARRAYSZ = 0x00000021;
|
||||
constexpr Elf64_Sxword DT_REL = 0x00000011;
|
||||
constexpr Elf64_Sxword DT_RELA = 0x00000007;
|
||||
constexpr Elf64_Sxword DT_SONAME = 0x0000000e;
|
||||
constexpr Elf64_Sxword DT_TEXTREL = 0x00000016;
|
||||
|
||||
constexpr Elf64_Sxword DT_HASH = 0x00000004;
|
||||
constexpr Elf64_Sxword DT_STRTAB = 0x00000005;
|
||||
constexpr Elf64_Sxword DT_STRSZ = 0x0000000a;
|
||||
constexpr Elf64_Sxword DT_SYMTAB = 0x00000006;
|
||||
constexpr Elf64_Sxword DT_SYMENT = 0x0000000b;
|
||||
constexpr Elf64_Sxword DT_PLTGOT = 0x00000003;
|
||||
constexpr Elf64_Sxword DT_PLTREL = 0x00000014;
|
||||
constexpr Elf64_Sxword DT_JMPREL = 0x00000017;
|
||||
constexpr Elf64_Sxword DT_PLTRELSZ = 0x00000002;
|
||||
constexpr Elf64_Sxword DT_RELASZ = 0x00000008;
|
||||
constexpr Elf64_Sxword DT_RELAENT = 0x00000009;
|
||||
constexpr Elf64_Sxword DT_RELACOUNT = 0x6ffffff9;
|
||||
|
||||
constexpr Elf64_Sxword DT_NULL = 0;
|
||||
|
||||
constexpr Elf64_Word R_X86_64_64 = 1;
|
||||
constexpr Elf64_Word R_X86_64_GLOB_DAT = 6;
|
||||
constexpr Elf64_Word R_X86_64_JUMP_SLOT = 7;
|
||||
constexpr Elf64_Word R_X86_64_RELATIVE = 8;
|
||||
constexpr Elf64_Word R_X86_64_DTPMOD64 = 16;
|
||||
|
||||
constexpr uint8_t STB_LOCAL = 0;
|
||||
constexpr uint8_t STB_GLOBAL = 1;
|
||||
constexpr uint8_t STB_WEAK = 2;
|
||||
|
||||
constexpr uint8_t STT_NOTYPE = 0;
|
||||
constexpr uint8_t STT_OBJECT = 1;
|
||||
constexpr uint8_t STT_FUNC = 2;
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
struct SelfHeader {
|
||||
uint8_t ident[12];
|
||||
uint16_t size1;
|
||||
uint16_t size2;
|
||||
uint64_t file_size;
|
||||
uint16_t segments_num;
|
||||
uint16_t unknown;
|
||||
uint32_t pad;
|
||||
};
|
||||
|
||||
struct SelfSegment {
|
||||
uint64_t type;
|
||||
uint64_t offset;
|
||||
uint64_t compressed_size;
|
||||
uint64_t decompressed_size;
|
||||
};
|
||||
|
||||
struct Elf64_Ehdr // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
uint8_t e_ident[EI_NIDENT]; /* ELF identification */
|
||||
Elf64_Half e_type; /* Object file type */
|
||||
Elf64_Half e_machine; /* Machine type */
|
||||
Elf64_Word e_version; /* Object file version */
|
||||
Elf64_Addr e_entry; /* Entry point address */
|
||||
Elf64_Off e_phoff; /* Program header offset */
|
||||
Elf64_Off e_shoff; /* Section header offset */
|
||||
Elf64_Word e_flags; /* Processor-specific flags */
|
||||
Elf64_Half e_ehsize; /* ELF header size */
|
||||
Elf64_Half e_phentsize; /* Size of program header entry */
|
||||
Elf64_Half e_phnum; /* Number of program header entries */
|
||||
Elf64_Half e_shentsize; /* Size of section header entry */
|
||||
Elf64_Half e_shnum; /* Number of section header entries */
|
||||
Elf64_Half e_shstrndx; /* Section name string table index */
|
||||
};
|
||||
|
||||
struct Elf64_Phdr // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
Elf64_Word p_type; /* Type of segment */
|
||||
Elf64_Word p_flags; /* Segment attributes */
|
||||
Elf64_Off p_offset; /* Offset in file */
|
||||
Elf64_Addr p_vaddr; /* Virtual address in memory */
|
||||
Elf64_Addr p_paddr; /* Reserved */
|
||||
Elf64_Xword p_filesz; /* Size of segment in file */
|
||||
Elf64_Xword p_memsz; /* Size of segment in memory */
|
||||
Elf64_Xword p_align; /* Alignment of segment */
|
||||
};
|
||||
|
||||
struct Elf64_Shdr // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
Elf64_Word sh_name; /* Section name */
|
||||
Elf64_Word sh_type; /* Section type */
|
||||
Elf64_Xword sh_flags; /* Section attributes */
|
||||
Elf64_Addr sh_addr; /* Virtual address in memory */
|
||||
Elf64_Off sh_offset; /* Offset in file */
|
||||
Elf64_Xword sh_size; /* Size of section */
|
||||
Elf64_Word sh_link; /* Link to other section */
|
||||
Elf64_Word sh_info; /* Miscellaneous information */
|
||||
Elf64_Xword sh_addralign; /* Address alignment boundary */
|
||||
Elf64_Xword sh_entsize; /* Size of entries, if section has table */
|
||||
};
|
||||
|
||||
struct Elf64_Dyn // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
Elf64_Sxword d_tag;
|
||||
union {
|
||||
Elf64_Xword d_val;
|
||||
Elf64_Addr d_ptr;
|
||||
} d_un;
|
||||
};
|
||||
|
||||
struct Elf64_Sym // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
[[nodiscard]] unsigned char GetBind() const { return st_info >> 4u; }
|
||||
[[nodiscard]] unsigned char GetType() const { return st_info & 0xfu; }
|
||||
|
||||
Elf64_Word st_name;
|
||||
unsigned char st_info;
|
||||
unsigned char st_other;
|
||||
Elf64_Half st_shndx;
|
||||
Elf64_Addr st_value;
|
||||
Elf64_Xword st_size;
|
||||
};
|
||||
|
||||
// struct Elf64_Rel // NOLINT(readability-identifier-naming)
|
||||
//{
|
||||
// Elf64_Addr r_offset;
|
||||
// Elf64_Xword r_info;
|
||||
//};
|
||||
|
||||
struct Elf64_Rela // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
[[nodiscard]] Elf64_Word GetSymbol() const { return static_cast<Elf64_Word>(r_info >> 32u); }
|
||||
[[nodiscard]] Elf64_Word GetType() const {
|
||||
return static_cast<Elf64_Word>(r_info & 0xffffffff);
|
||||
}
|
||||
|
||||
Elf64_Addr r_offset;
|
||||
Elf64_Xword r_info;
|
||||
Elf64_Sxword r_addend;
|
||||
};
|
||||
|
||||
struct tls_info_t // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
uint64_t addr;
|
||||
uint64_t filesz;
|
||||
uint64_t memsz;
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
class Elf64 {
|
||||
public:
|
||||
Elf64() = default;
|
||||
virtual ~Elf64();
|
||||
|
||||
void Open(const std::filesystem::path& file_name);
|
||||
void Save(const std::filesystem::path& file_name);
|
||||
|
||||
void DbgDump(const std::string& folder);
|
||||
|
||||
const char* GetSectionName(int index) const;
|
||||
|
||||
[[nodiscard]] bool IsSelf() const;
|
||||
[[nodiscard]] bool IsValid() const;
|
||||
[[nodiscard]] bool IsShared() const;
|
||||
[[nodiscard]] bool IsNextGen() const;
|
||||
|
||||
void LoadSegment(uint64_t vaddr, uint64_t file_offset, uint64_t size);
|
||||
|
||||
uint64_t GetEntry();
|
||||
|
||||
[[nodiscard]] const Elf64_Dyn* GetDynValue(Elf64_Sxword tag) const;
|
||||
[[nodiscard]] std::vector<const Elf64_Dyn*> GetDynList(Elf64_Sxword tag) const;
|
||||
[[nodiscard]] bool HasDynValue(Elf64_Sxword tag) const { return GetDynValue(tag) != nullptr; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(Elf64);
|
||||
|
||||
[[nodiscard]] const Elf64_Dyn* GetDynamic() const {
|
||||
return reinterpret_cast<const Elf64_Dyn*>(m_dynamic.get());
|
||||
}
|
||||
[[nodiscard]] const Elf64_Ehdr* GetEhdr() const { return m_ehdr.get(); }
|
||||
[[nodiscard]] const Elf64_Phdr* GetPhdr() const { return m_phdr.get(); }
|
||||
[[nodiscard]] const Elf64_Shdr* GetShdr() const { return m_shdr.get(); }
|
||||
[[nodiscard]] char* GetStrTable() const { return m_str_table.get(); }
|
||||
|
||||
template <class T>
|
||||
[[nodiscard]] T GetDynamicData(uint64_t offset) const {
|
||||
return (m_dynamic_data == nullptr ? nullptr
|
||||
: reinterpret_cast<T>(m_dynamic_data.get() + offset));
|
||||
}
|
||||
|
||||
private:
|
||||
void Clear();
|
||||
|
||||
std::unique_ptr<Common::File> m_f;
|
||||
std::unique_ptr<SelfHeader> m_self;
|
||||
std::unique_ptr<SelfSegment[]> m_self_segments;
|
||||
std::unique_ptr<Elf64_Ehdr> m_ehdr;
|
||||
std::unique_ptr<Elf64_Phdr[]> m_phdr;
|
||||
std::unique_ptr<Elf64_Shdr[]> m_shdr;
|
||||
std::unique_ptr<uint8_t[]> m_dynamic;
|
||||
std::unique_ptr<uint8_t[]> m_dynamic_data;
|
||||
std::unique_ptr<char[]> m_str_table;
|
||||
uint32_t m_str_table_size = 0;
|
||||
// uint64_t m_base_vaddr = 0;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_LOADER_ELF_H_ */
|
||||
@@ -0,0 +1,167 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_JIT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LOADER_JIT_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
|
||||
namespace Loader::Jit {
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
struct JmpWithIndex {
|
||||
void SetIndex(uint32_t index) { *reinterpret_cast<uint32_t*>(&code[1]) = index; }
|
||||
|
||||
void SetFunc(void* handler) {
|
||||
auto func_addr = reinterpret_cast<int64_t>(handler);
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[10]);
|
||||
auto offset64 = func_addr - rip_addr;
|
||||
auto offset32 = static_cast<uint32_t>(static_cast<uint64_t>(offset64) & 0xffffffffu);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&code[6]) = offset32;
|
||||
}
|
||||
|
||||
static uint64_t GetSize() { return 16; }
|
||||
|
||||
// 68 00 00 00 00 push <index>
|
||||
// E9 E0 FF FF FF jmp <handler>
|
||||
uint8_t code[16] = {0x68, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00,
|
||||
0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
|
||||
};
|
||||
|
||||
struct CallPlt {
|
||||
explicit CallPlt(uint32_t table_size) {
|
||||
for (uint32_t index = 0; index < table_size; index++) {
|
||||
auto* c = new (&code[32] + JmpWithIndex::GetSize() * index) JmpWithIndex;
|
||||
c->SetIndex(index);
|
||||
c->SetFunc(this);
|
||||
}
|
||||
}
|
||||
|
||||
void SetPltGot(uint64_t vaddr) { *reinterpret_cast<uint64_t*>(&code[2]) = vaddr; }
|
||||
|
||||
uint64_t GetAddr(uint32_t index) {
|
||||
return reinterpret_cast<uint64_t>(&code[32] + JmpWithIndex::GetSize() * index);
|
||||
}
|
||||
|
||||
static uint64_t GetSize(uint32_t table_size) {
|
||||
return 32 + JmpWithIndex::GetSize() * table_size;
|
||||
}
|
||||
|
||||
// 0: 49 bb 88 77 66 55 44 movabs r11,0x1122334455667788
|
||||
// 7: 33 22 11
|
||||
// a: 41 ff 73 08 push QWORD PTR [r11+0x8]
|
||||
// e: 41 ff 63 10 jmp QWORD PTR [r11+0x10]
|
||||
uint8_t code[32] = {0x49, 0xBB, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x41, 0xFF,
|
||||
0x73, 0x08, 0x41, 0xFF, 0x63, 0x10, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
|
||||
};
|
||||
|
||||
struct JmpRax {
|
||||
template <class Handler>
|
||||
void SetFunc(Handler func) {
|
||||
*reinterpret_cast<Handler*>(&code[2]) = func;
|
||||
}
|
||||
|
||||
// mov rax, 0x1122334455667788
|
||||
// jmp rax
|
||||
uint8_t code[16] = {0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xFF, 0xE0};
|
||||
};
|
||||
|
||||
struct Call9 {
|
||||
template <class Handler>
|
||||
void SetFunc(Handler func) {
|
||||
auto func_addr = reinterpret_cast<int64_t>(reinterpret_cast<void*>(func));
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[5]);
|
||||
auto offset64 = func_addr - rip_addr;
|
||||
auto offset32 = static_cast<uint32_t>(static_cast<uint64_t>(offset64) & 0xffffffffu);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&code[1]) = offset32;
|
||||
}
|
||||
|
||||
void SetOutputReg(uint8_t reg) { code[7] = 0xc0u | (reg & 7u); }
|
||||
|
||||
static uint64_t GetSize() { return 9; }
|
||||
|
||||
// call func
|
||||
// mov rax,rax
|
||||
// nop
|
||||
uint8_t code[9] = {0xE8, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xC0, 0x90};
|
||||
};
|
||||
|
||||
struct TlsRegStub {
|
||||
template <class Handler>
|
||||
void SetFunc(Handler func) {
|
||||
auto func_addr = reinterpret_cast<int64_t>(reinterpret_cast<void*>(func));
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[13]);
|
||||
auto offset64 = func_addr - rip_addr;
|
||||
auto offset32 = static_cast<uint32_t>(static_cast<uint64_t>(offset64) & 0xffffffffu);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&code[9]) = offset32;
|
||||
}
|
||||
|
||||
void SetOutputReg(uint8_t reg) { code[15] = 0xc0u | (reg & 7u); }
|
||||
|
||||
static uint64_t GetOffset(uint8_t reg) {
|
||||
return 0x100 + static_cast<uint64_t>(reg) * GetSize();
|
||||
}
|
||||
static uint64_t GetSize() { return 32; }
|
||||
|
||||
// sub rsp,0x80
|
||||
// push rax
|
||||
// call safe_call
|
||||
// mov <reg>,rax
|
||||
// pop rax
|
||||
// add rsp,0x80
|
||||
// ret
|
||||
uint8_t code[32] = {0x48, 0x81, 0xEC, 0x80, 0x00, 0x00, 0x00, 0x50, 0xE8, 0x00, 0x00,
|
||||
0x00, 0x00, 0x48, 0x89, 0xC0, 0x58, 0x48, 0x81, 0xC4, 0x80, 0x00,
|
||||
0x00, 0x00, 0xC3, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
|
||||
};
|
||||
|
||||
struct SafeCall {
|
||||
using func_t = KYTY_MS_ABI uint8_t* (*)();
|
||||
|
||||
void SetFunc(func_t func) { *reinterpret_cast<func_t*>(&code[0x22]) = func; }
|
||||
|
||||
static uint64_t GetSize() { return 0x1000; }
|
||||
|
||||
uint8_t code[0x90] = {
|
||||
/*00*/ 0x48, 0x81, 0xec, 0x80, 0x00, 0x00, 0x00, // sub rsp,0x80
|
||||
/*07*/ 0x9c, // pushfq
|
||||
/*08*/ 0x51, // push rcx
|
||||
/*09*/ 0x52, // push rdx
|
||||
/*0a*/ 0x41, 0x50, // push r8
|
||||
/*0c*/ 0x41, 0x51, // push r9
|
||||
/*0e*/ 0x41, 0x52, // push r10
|
||||
/*10*/ 0x41, 0x53, // push r11
|
||||
/*12*/ 0x53, // push rbx
|
||||
/*13*/ 0x57, // push rdi
|
||||
/*14*/ 0x56, // push rsi
|
||||
/*15*/ 0x48, 0x89, 0xe3, // mov rbx,rsp
|
||||
/*18*/ 0x48, 0x83, 0xe4, 0xf0, // and rsp,0xfffffffffffffff0
|
||||
/*1c*/ 0x48, 0x83, 0xec, 0x20, // sub rsp,0x20
|
||||
/*20*/ 0x48, 0xb9, 0x88, 0x77, 0x66, 0x55, 0x44,
|
||||
0x33, 0x22, 0x11, // movabs rcx,0x1122334455667788
|
||||
/*2a*/ 0xff, 0xd1, // call rcx
|
||||
/*2c*/ 0x48, 0x89, 0xc1, // mov rcx,rax
|
||||
/*2f*/ 0x48, 0x89, 0xdc, // mov rsp,rbx
|
||||
/*32*/ 0x48, 0x89, 0xc8, // mov rax,rcx
|
||||
/*35*/ 0x5e, // pop rsi
|
||||
/*36*/ 0x5f, // pop rdi
|
||||
/*37*/ 0x5b, // pop rbx
|
||||
/*38*/ 0x41, 0x5b, // pop r11
|
||||
/*3a*/ 0x41, 0x5a, // pop r10
|
||||
/*3c*/ 0x41, 0x59, // pop r9
|
||||
/*3e*/ 0x41, 0x58, // pop r8
|
||||
/*40*/ 0x5a, // pop rdx
|
||||
/*41*/ 0x59, // pop rcx
|
||||
/*42*/ 0x9d, // popfq
|
||||
/*43*/ 0x48, 0x81, 0xc4, 0x80, 0x00, 0x00,
|
||||
0x00, // add rsp,0x80
|
||||
/*4a*/ 0xc3, // ret
|
||||
};
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
} // namespace Loader::Jit
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_LOADER_JIT_H_ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_RUNTIMELINKER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LOADER_RUNTIMELINKER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Loader {
|
||||
|
||||
class Elf64;
|
||||
struct Elf64_Sym;
|
||||
struct Elf64_Rela;
|
||||
class RuntimeLinker;
|
||||
|
||||
using module_func_t = KYTY_SYSV_ABI int (*)(size_t args, const void* argp);
|
||||
using application_heap_free_func_t = KYTY_SYSV_ABI void (*)(void*);
|
||||
using application_heap_malloc_func_t = KYTY_SYSV_ABI void* (*)(uint64_t);
|
||||
using application_heap_posix_memalign_func_t = KYTY_SYSV_ABI int (*)(void**, uint64_t, uint64_t);
|
||||
|
||||
struct ModuleId {
|
||||
bool operator==(const ModuleId& other) const {
|
||||
return version_major == other.version_major && version_minor == other.version_minor &&
|
||||
name == other.name;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
int version_major;
|
||||
int version_minor;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct LibraryId {
|
||||
bool operator==(const LibraryId& other) const {
|
||||
return version == other.version && name == other.name;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
int version;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct ThreadLocalStorage {
|
||||
struct Block {
|
||||
uint8_t* ptr = nullptr;
|
||||
application_heap_free_func_t free_func = nullptr;
|
||||
bool vm_alloc = false;
|
||||
};
|
||||
|
||||
~ThreadLocalStorage();
|
||||
|
||||
uint64_t image_vaddr = 0;
|
||||
uint64_t init_size = 0;
|
||||
uint64_t image_size = 0;
|
||||
uint64_t tcb_offset = 0;
|
||||
uint64_t handler_vaddr = 0;
|
||||
|
||||
std::vector<uint8_t> init_image;
|
||||
std::unordered_map<int, Block> tlss;
|
||||
Common::Mutex mutex;
|
||||
};
|
||||
|
||||
struct DynamicInfo {
|
||||
void* hash_table = nullptr;
|
||||
uint64_t hash_table_size = 0;
|
||||
|
||||
char* str_table = nullptr;
|
||||
uint64_t str_table_size = 0;
|
||||
|
||||
Elf64_Sym* symbol_table = nullptr;
|
||||
uint64_t symbol_table_total_size = 0;
|
||||
uint64_t symbol_table_entry_size = 0;
|
||||
|
||||
uint64_t init_vaddr = 0;
|
||||
uint64_t fini_vaddr = 0;
|
||||
uint64_t init_array_vaddr = 0;
|
||||
uint64_t fini_array_vaddr = 0;
|
||||
uint64_t preinit_array_vaddr = 0;
|
||||
uint64_t init_array_size = 0;
|
||||
uint64_t fini_array_size = 0;
|
||||
uint64_t preinit_array_size = 0;
|
||||
uint64_t pltgot_vaddr = 0;
|
||||
|
||||
Elf64_Rela* jmprela_table = nullptr;
|
||||
uint64_t jmprela_table_size = 0;
|
||||
|
||||
Elf64_Rela* rela_table = nullptr;
|
||||
uint64_t rela_table_total_size = 0;
|
||||
uint64_t rela_table_entry_size = 0;
|
||||
|
||||
uint64_t relative_count = 0;
|
||||
|
||||
uint64_t debug = 0;
|
||||
uint64_t textrel = 0;
|
||||
uint64_t flags = 0;
|
||||
|
||||
const char* so_name = nullptr;
|
||||
|
||||
std::vector<const char*> needed;
|
||||
std::vector<ModuleId> export_modules;
|
||||
std::vector<ModuleId> import_modules;
|
||||
std::vector<LibraryId> export_libs;
|
||||
std::vector<LibraryId> import_libs;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
Program();
|
||||
~Program();
|
||||
|
||||
int32_t unique_id = -1;
|
||||
RuntimeLinker* rt = nullptr;
|
||||
std::filesystem::path file_name;
|
||||
std::unique_ptr<Elf64> elf;
|
||||
std::unique_ptr<DynamicInfo> dynamic_info;
|
||||
uint64_t base_vaddr = 0;
|
||||
uint64_t base_size = 0;
|
||||
uint64_t base_size_aligned = 0;
|
||||
uint64_t mapped_size = 0;
|
||||
std::unique_ptr<SymbolDatabase> export_symbols;
|
||||
std::unique_ptr<SymbolDatabase> import_symbols;
|
||||
ThreadLocalStorage tls;
|
||||
bool fail_if_global_not_resolved = true;
|
||||
bool dbg_print_reloc = false;
|
||||
bool relocated = false;
|
||||
uint64_t proc_param_vaddr = 0;
|
||||
uint64_t custom_call_plt_vaddr = 0;
|
||||
uint32_t custom_call_plt_num = 0;
|
||||
};
|
||||
|
||||
class RuntimeLinker {
|
||||
public:
|
||||
RuntimeLinker();
|
||||
virtual ~RuntimeLinker();
|
||||
void Clear();
|
||||
|
||||
KYTY_CLASS_NO_COPY(RuntimeLinker);
|
||||
|
||||
void DbgDump(const std::string& folder);
|
||||
|
||||
Program* LoadProgram(const std::filesystem::path& elf_name);
|
||||
void SaveMainProgram(const std::filesystem::path& elf_name);
|
||||
void SaveProgram(Program* program, const std::filesystem::path& elf_name);
|
||||
void UnloadProgram(Program* program);
|
||||
|
||||
[[nodiscard]] uint64_t GetEntry();
|
||||
[[nodiscard]] uint64_t GetProcParam();
|
||||
|
||||
void RelocateAll();
|
||||
void RelocateProgram(Program* program);
|
||||
|
||||
void Execute();
|
||||
int StartModule(Program* program, size_t args, const void* argp, module_func_t func);
|
||||
int StopModule(Program* program, size_t args, const void* argp, module_func_t func);
|
||||
void StartAllModules();
|
||||
void StopAllModules();
|
||||
void DeleteTlss(int thread_id);
|
||||
void SetApplicationHeapApi(void* const api[10]);
|
||||
void* ApplicationHeapMalloc(uint64_t size);
|
||||
void* ApplicationHeapMemalign(uint64_t alignment, uint64_t size);
|
||||
|
||||
void Resolve(const std::string& name, SymbolType type, Program* program, SymbolRecord* out_info,
|
||||
bool* bind_self);
|
||||
bool ResolveLoadedSymbolByNid(const std::string& nid, SymbolType type, SymbolRecord* out_info);
|
||||
|
||||
SymbolDatabase* Symbols() { return m_symbols.get(); }
|
||||
|
||||
static uint64_t ReadFromElf(Program* program, uint64_t vaddr);
|
||||
Program* FindProgramByAddr(uint64_t vaddr);
|
||||
Program* FindProgramById(int32_t id);
|
||||
Program* FindProgramByFileName(const std::filesystem::path& elf_name);
|
||||
|
||||
static uint8_t* TlsGetAddr(Program* program);
|
||||
static void DeleteTls(Program* program, int thread_id);
|
||||
|
||||
void StackTrace(uint64_t frame_ptr);
|
||||
|
||||
private:
|
||||
static void LoadProgramToMemory(Program* program);
|
||||
static void ParseProgramDynamicInfo(Program* program);
|
||||
static void CreateSymbolDatabase(Program* program);
|
||||
static void Relocate(Program* program);
|
||||
static void DeleteProgram(Program* program);
|
||||
static void SetupTlsHandler(Program* program);
|
||||
void PreloadAdjacentPrograms();
|
||||
|
||||
Program* FindProgram(const ModuleId& m, const LibraryId& l);
|
||||
|
||||
static const ModuleId* FindModule(const Program& program, const std::string& id);
|
||||
static const LibraryId* FindLibrary(const Program& program, const std::string& id);
|
||||
|
||||
std::vector<Program*> m_programs;
|
||||
std::unique_ptr<SymbolDatabase> m_symbols;
|
||||
bool m_relocated = false;
|
||||
Common::Mutex m_mutex;
|
||||
|
||||
application_heap_malloc_func_t m_application_heap_malloc = nullptr;
|
||||
application_heap_free_func_t m_application_heap_free = nullptr;
|
||||
application_heap_posix_memalign_func_t m_application_heap_posix_memalign = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_LOADER_RUNTIMELINKER_H_ */
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/magicEnum.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Loader {
|
||||
|
||||
constexpr char LIB_PREFIX[] = "libSce";
|
||||
|
||||
static std::string UpdateName(const std::string& str) {
|
||||
return Common::StartsWith(str, LIB_PREFIX) ? Common::RemoveFirst(str, 6) : str;
|
||||
}
|
||||
|
||||
std::string SymbolDatabase::GenerateName(const SymbolResolve& s) {
|
||||
auto library = UpdateName(s.library);
|
||||
auto module = UpdateName(s.module);
|
||||
return fmt::format("{}[{}_v{}][{}_v{}.{}][{}]", s.name.c_str(), library.c_str(),
|
||||
s.library_version, module.c_str(), s.module_version_major,
|
||||
s.module_version_minor, Common::EnumName(s.type).c_str());
|
||||
}
|
||||
|
||||
void SymbolDatabase::Add(const SymbolResolve& s, uint64_t vaddr) {
|
||||
SymbolRecord r {};
|
||||
r.name = GenerateName(s);
|
||||
r.vaddr = vaddr;
|
||||
m_map.insert_or_assign(r.name, m_symbols.size());
|
||||
m_symbols.push_back(r);
|
||||
}
|
||||
|
||||
void SymbolDatabase::Add(const SymbolResolve& s, uint64_t vaddr, const std::string& dbg_name) {
|
||||
SymbolRecord r {};
|
||||
r.name = GenerateName(s);
|
||||
r.vaddr = vaddr;
|
||||
r.dbg_name = dbg_name;
|
||||
m_map.insert_or_assign(r.name, m_symbols.size());
|
||||
m_symbols.push_back(r);
|
||||
}
|
||||
|
||||
void SymbolDatabase::DbgDump(const std::string& folder, const std::string& file_name) {
|
||||
auto folder_str = Common::FixDirectorySlash(folder);
|
||||
|
||||
Common::File::CreateDirectories(folder_str);
|
||||
|
||||
Common::File f;
|
||||
f.Create(folder_str + file_name);
|
||||
|
||||
for (const auto& sym: m_symbols) {
|
||||
f.Printf("%" PRIx64 " %s\n", sym.vaddr, sym.name.c_str());
|
||||
}
|
||||
|
||||
f.Close();
|
||||
}
|
||||
|
||||
const SymbolRecord* SymbolDatabase::Find(const SymbolResolve& s) const {
|
||||
auto it = m_map.find(GenerateName(s));
|
||||
if (it == m_map.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = it->second;
|
||||
if (index >= m_symbols.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_symbols[index];
|
||||
}
|
||||
|
||||
const SymbolRecord* SymbolDatabase::FindByNid(const std::string& nid, SymbolType type) const {
|
||||
auto prefix = nid + "[";
|
||||
auto suffix = fmt::format("[{}]", Common::EnumName(type).c_str());
|
||||
|
||||
for (const auto& symbol: m_symbols) {
|
||||
if (Common::StartsWith(symbol.name, prefix) && Common::EndsWith(symbol.name, suffix)) {
|
||||
return &symbol;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SymbolRecord* SymbolDatabase::FindByName(const std::string& name, SymbolType type) const {
|
||||
auto prefix = name + "[";
|
||||
auto suffix = fmt::format("[{}]", Common::EnumName(type).c_str());
|
||||
|
||||
for (const auto& symbol: m_symbols) {
|
||||
if (Common::StartsWith(symbol.name, prefix) && Common::EndsWith(symbol.name, suffix)) {
|
||||
return &symbol;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_SYMBOLDATABASE_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LOADER_SYMBOLDATABASE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Loader {
|
||||
|
||||
enum class SymbolType {
|
||||
Unknown,
|
||||
Func,
|
||||
Object,
|
||||
TlsModule,
|
||||
NoType,
|
||||
};
|
||||
|
||||
struct SymbolRecord {
|
||||
std::string name;
|
||||
std::string dbg_name;
|
||||
uint64_t vaddr;
|
||||
};
|
||||
|
||||
struct SymbolResolve {
|
||||
std::string name;
|
||||
std::string library;
|
||||
int library_version;
|
||||
std::string module;
|
||||
int module_version_major;
|
||||
int module_version_minor;
|
||||
SymbolType type;
|
||||
};
|
||||
|
||||
class SymbolDatabase {
|
||||
public:
|
||||
SymbolDatabase() = default;
|
||||
virtual ~SymbolDatabase() = default;
|
||||
|
||||
void Add(const SymbolResolve& s, uint64_t vaddr);
|
||||
void Add(const SymbolResolve& s, uint64_t vaddr, const std::string& dbg_name);
|
||||
|
||||
[[nodiscard]] const SymbolRecord* Find(const SymbolResolve& s) const;
|
||||
[[nodiscard]] const SymbolRecord* FindByNid(const std::string& nid, SymbolType type) const;
|
||||
[[nodiscard]] const SymbolRecord* FindByName(const std::string& name, SymbolType type) const;
|
||||
|
||||
void DbgDump(const std::string& folder, const std::string& file_name);
|
||||
|
||||
KYTY_CLASS_NO_COPY(SymbolDatabase);
|
||||
|
||||
static std::string GenerateName(const SymbolResolve& s);
|
||||
|
||||
private:
|
||||
std::vector<SymbolRecord> m_symbols;
|
||||
std::unordered_map<std::string, size_t> m_map;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_LOADER_SYMBOLDATABASE_H_ */
|
||||
@@ -0,0 +1,677 @@
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/threads.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Loader {
|
||||
|
||||
class Psf {
|
||||
public:
|
||||
Psf() = default;
|
||||
virtual ~Psf();
|
||||
|
||||
KYTY_CLASS_NO_COPY(Psf);
|
||||
|
||||
void Open(const std::filesystem::path& file_name);
|
||||
void OpenChunkDefs(const std::filesystem::path& file_name);
|
||||
void Close();
|
||||
|
||||
bool IsValid();
|
||||
|
||||
bool GetParamInt(const char* name, int32_t* value);
|
||||
bool GetParamString(const char* name, std::string* value);
|
||||
bool GetParamString(const char* name, char* value, size_t value_size);
|
||||
uint64_t GetFlexibleMemorySize();
|
||||
|
||||
void DbgPrint();
|
||||
|
||||
private:
|
||||
enum class ParamType {
|
||||
Text,
|
||||
Int,
|
||||
};
|
||||
|
||||
struct Param {
|
||||
std::string name;
|
||||
ParamType type = ParamType::Text;
|
||||
std::string string_value;
|
||||
std::string string_utf8;
|
||||
int32_t int_value = 0;
|
||||
};
|
||||
|
||||
void OpenJson(const std::filesystem::path& file_name);
|
||||
void AddParamString(const std::string& name, const std::string& value);
|
||||
void AddParamInt(const std::string& name, int32_t value);
|
||||
Param* FindParam(const char* name);
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
bool m_opened = false;
|
||||
std::vector<Param> m_params;
|
||||
uint64_t m_flexible_memory_size = 0;
|
||||
};
|
||||
|
||||
class PlayGo {
|
||||
public:
|
||||
PlayGo() = default;
|
||||
virtual ~PlayGo();
|
||||
|
||||
KYTY_CLASS_NO_COPY(PlayGo);
|
||||
|
||||
void Open(const std::filesystem::path& file_name);
|
||||
void OpenChunkDefs(const std::filesystem::path& file_name);
|
||||
void OpenChunkManifest(const std::filesystem::path& file_name);
|
||||
void Close();
|
||||
|
||||
bool IsValid();
|
||||
|
||||
bool GetChunksNum(uint32_t* num);
|
||||
|
||||
void DbgPrint();
|
||||
|
||||
private:
|
||||
Common::File m_f;
|
||||
Common::Mutex m_mutex;
|
||||
bool m_opened = false;
|
||||
uint16_t m_chunks_num = 0;
|
||||
};
|
||||
|
||||
struct SystemContent {
|
||||
std::filesystem::path psf_path;
|
||||
Psf psf;
|
||||
std::filesystem::path playgo_path;
|
||||
PlayGo playgo;
|
||||
std::filesystem::path icon_path;
|
||||
};
|
||||
|
||||
Psf::~Psf() {
|
||||
Close();
|
||||
}
|
||||
|
||||
void Psf::Open(const std::filesystem::path& file_name) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Close();
|
||||
|
||||
OpenJson(file_name);
|
||||
}
|
||||
|
||||
void Psf::OpenJson(const std::filesystem::path& file_name) {
|
||||
Common::File f;
|
||||
|
||||
f.Open(file_name, Common::File::Mode::Read);
|
||||
|
||||
if (f.IsInvalid()) {
|
||||
LOGF("Can't open %s\n", Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto buf = f.ReadWholeBuffer();
|
||||
if (buf.Size() == 0) {
|
||||
LOGF("invalid json file: %s\n", Common::PathToString(file_name).c_str());
|
||||
f.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string default_language;
|
||||
std::vector<std::string> localized_languages;
|
||||
std::vector<std::string> localized_titles;
|
||||
|
||||
const auto* json_begin = reinterpret_cast<const char*>(buf.GetDataConst());
|
||||
const auto* json_end = json_begin + buf.Size();
|
||||
auto param_json = nlohmann::ordered_json::parse(json_begin, json_end, nullptr, false);
|
||||
if (param_json.is_discarded() || !param_json.is_object()) {
|
||||
LOGF("invalid json file: %s\n", Common::PathToString(file_name).c_str());
|
||||
f.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
auto add_string_param = [&](const char* json_key, const char* param_key) {
|
||||
auto it = param_json.find(json_key);
|
||||
if (it != param_json.end() && it->is_string()) {
|
||||
AddParamString(param_key, it->get_ref<const std::string&>());
|
||||
}
|
||||
};
|
||||
|
||||
auto add_int_param = [&](const char* json_key, const char* param_key) {
|
||||
auto it = param_json.find(json_key);
|
||||
if (it == param_json.end() || !it->is_number_integer()) {
|
||||
return;
|
||||
}
|
||||
int64_t value = 0;
|
||||
if (it->is_number_unsigned()) {
|
||||
value = static_cast<int64_t>(it->get<uint64_t>());
|
||||
} else {
|
||||
value = it->get<int64_t>();
|
||||
}
|
||||
AddParamInt(param_key, static_cast<int32_t>(value));
|
||||
};
|
||||
|
||||
add_string_param("titleId", "TITLE_ID");
|
||||
add_string_param("contentId", "CONTENT_ID");
|
||||
add_string_param("contentVersion", "APP_VER");
|
||||
add_string_param("appVersion", "APP_VER");
|
||||
add_int_param("userDefinedParam1", "USER_DEFINED_PARAM_1");
|
||||
add_int_param("userDefinedParam2", "USER_DEFINED_PARAM_2");
|
||||
add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3");
|
||||
add_int_param("userDefinedParam4", "USER_DEFINED_PARAM_4");
|
||||
|
||||
if (auto kernel = param_json.find("kernel");
|
||||
kernel != param_json.end() && kernel->is_object()) {
|
||||
if (auto it = kernel->find("flexibleMemorySize"); it != kernel->end()) {
|
||||
m_flexible_memory_size = it->get<uint64_t>();
|
||||
}
|
||||
}
|
||||
|
||||
auto localized = param_json.find("localizedParameters");
|
||||
if (localized != param_json.end() && localized->is_object()) {
|
||||
auto default_it = localized->find("defaultLanguage");
|
||||
if (default_it != localized->end() && default_it->is_string()) {
|
||||
default_language = default_it->get_ref<const std::string&>();
|
||||
}
|
||||
for (auto it = localized->begin(); it != localized->end(); ++it) {
|
||||
if (!it->is_object()) {
|
||||
continue;
|
||||
}
|
||||
auto title_it = it->find("titleName");
|
||||
if (title_it != it->end() && title_it->is_string()) {
|
||||
localized_languages.push_back(it.key());
|
||||
localized_titles.push_back(title_it->get_ref<const std::string&>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string title;
|
||||
for (size_t i = 0; i < localized_titles.size(); i++) {
|
||||
if (!default_language.empty() && localized_languages[i] == default_language) {
|
||||
title = localized_titles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (title.empty()) {
|
||||
for (size_t i = 0; i < localized_titles.size(); i++) {
|
||||
if (localized_languages[i] == "en-US") {
|
||||
title = localized_titles[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (title.empty() && !localized_titles.empty()) {
|
||||
title = localized_titles[0];
|
||||
}
|
||||
if (!title.empty()) {
|
||||
AddParamString("TITLE", title);
|
||||
}
|
||||
|
||||
f.Close();
|
||||
m_opened = true;
|
||||
}
|
||||
|
||||
void Psf::Close() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
m_params.clear();
|
||||
m_flexible_memory_size = 0;
|
||||
m_opened = false;
|
||||
}
|
||||
|
||||
bool Psf::IsValid() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
return m_opened;
|
||||
}
|
||||
|
||||
void Psf::DbgPrint() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (!m_opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& param: m_params) {
|
||||
LOGF("%s, ", param.name.c_str());
|
||||
|
||||
switch (param.type) {
|
||||
case ParamType::Text: {
|
||||
auto str = Common::ReplaceChar(param.string_value, '\n', ',');
|
||||
LOGF("string = %s\n", str.c_str());
|
||||
break;
|
||||
}
|
||||
case ParamType::Int:
|
||||
LOGF("int = 0x%08" PRIx32 "\n", static_cast<uint32_t>(param.int_value));
|
||||
break;
|
||||
default: EXIT("unknown type\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Psf::Param* Psf::FindParam(const char* name) {
|
||||
for (auto& param: m_params) {
|
||||
if (param.name == name) {
|
||||
return ¶m;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Psf::AddParamString(const std::string& name, const std::string& value) {
|
||||
auto* param = FindParam(name.c_str());
|
||||
if (param == nullptr) {
|
||||
m_params.push_back({});
|
||||
param = &m_params.back();
|
||||
param->name = name;
|
||||
}
|
||||
|
||||
param->type = ParamType::Text;
|
||||
param->string_utf8 = value;
|
||||
param->string_value = value;
|
||||
}
|
||||
|
||||
void Psf::AddParamInt(const std::string& name, int32_t value) {
|
||||
auto* param = FindParam(name.c_str());
|
||||
if (param == nullptr) {
|
||||
m_params.push_back({});
|
||||
param = &m_params.back();
|
||||
param->name = name;
|
||||
}
|
||||
|
||||
param->type = ParamType::Int;
|
||||
param->int_value = value;
|
||||
}
|
||||
|
||||
bool Psf::GetParamInt(const char* name, int32_t* value) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (!m_opened) {
|
||||
return false;
|
||||
}
|
||||
if (auto* param = FindParam(name); param != nullptr && param->type == ParamType::Int) {
|
||||
*value = param->int_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Psf::GetParamString(const char* name, std::string* value) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (!m_opened) {
|
||||
return false;
|
||||
}
|
||||
if (auto* param = FindParam(name); param != nullptr && param->type == ParamType::Text) {
|
||||
*value = param->string_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Psf::GetParamString(const char* name, char* value, size_t value_size) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (!m_opened) {
|
||||
return false;
|
||||
}
|
||||
if (auto* param = FindParam(name); param != nullptr && param->type == ParamType::Text &&
|
||||
param->string_utf8.size() + 1 <= value_size) {
|
||||
std::memcpy(value, param->string_utf8.c_str(), param->string_utf8.size() + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t Psf::GetFlexibleMemorySize() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
return m_flexible_memory_size;
|
||||
}
|
||||
|
||||
PlayGo::~PlayGo() {
|
||||
Close();
|
||||
}
|
||||
|
||||
void PlayGo::Open(const std::filesystem::path& file_name) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Close();
|
||||
|
||||
m_f.Open(file_name, Common::File::Mode::Read);
|
||||
|
||||
if (m_f.IsInvalid()) {
|
||||
LOGF("Can't open %s\n", Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t magic1 = 0;
|
||||
|
||||
m_f.Read(&magic1, 4);
|
||||
|
||||
if (magic1 != 0x6f676c70) {
|
||||
LOGF("invalid file: magic1 = %08" PRIx32 "\n", magic1);
|
||||
return;
|
||||
}
|
||||
|
||||
m_f.Seek(10);
|
||||
m_f.Read(&m_chunks_num, 2);
|
||||
|
||||
m_opened = true;
|
||||
}
|
||||
|
||||
static bool ParseXmlUintAttr(std::string_view tag, std::string_view attr, uint32_t& value) {
|
||||
const auto attr_pos = tag.find(attr);
|
||||
if (attr_pos == std::string_view::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto pos = attr_pos + attr.size();
|
||||
while (pos < tag.size() && (tag[pos] == ' ' || tag[pos] == '\t')) {
|
||||
pos++;
|
||||
}
|
||||
if (pos >= tag.size() || tag[pos] != '=') {
|
||||
return false;
|
||||
}
|
||||
pos++;
|
||||
while (pos < tag.size() && (tag[pos] == ' ' || tag[pos] == '\t')) {
|
||||
pos++;
|
||||
}
|
||||
if (pos >= tag.size() || (tag[pos] != '"' && tag[pos] != '\'')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char quote = tag[pos++];
|
||||
const auto start = pos;
|
||||
while (pos < tag.size() && tag[pos] >= '0' && tag[pos] <= '9') {
|
||||
pos++;
|
||||
}
|
||||
if (start == pos || pos >= tag.size() || tag[pos] != quote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t parsed = 0;
|
||||
const auto [ptr, ec] = std::from_chars(tag.data() + start, tag.data() + pos, parsed);
|
||||
if (ec != std::errc {} || ptr != tag.data() + pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayGo::OpenChunkDefs(const std::filesystem::path& file_name) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (m_opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::File f;
|
||||
f.Open(file_name, Common::File::Mode::Read);
|
||||
|
||||
if (f.IsInvalid()) {
|
||||
LOGF("Can't open %s\n", Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto buf = f.ReadWholeBuffer();
|
||||
f.Close();
|
||||
|
||||
if (buf.Size() == 0) {
|
||||
LOGF("invalid file: %s\n", Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view xml(reinterpret_cast<const char*>(buf.GetDataConst()), buf.Size());
|
||||
|
||||
uint32_t max_chunk_id = 0;
|
||||
bool found = false;
|
||||
|
||||
if (const auto chunks_pos = xml.find("<chunks"); chunks_pos != std::string_view::npos) {
|
||||
const auto tag_end = xml.find('>', chunks_pos);
|
||||
if (tag_end != std::string_view::npos) {
|
||||
uint32_t default_chunk = 0;
|
||||
if (ParseXmlUintAttr(xml.substr(chunks_pos, tag_end - chunks_pos), "default_chunk",
|
||||
default_chunk)) {
|
||||
max_chunk_id = std::max(max_chunk_id, default_chunk);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = xml.find("<chunk", pos)) != std::string_view::npos) {
|
||||
const auto name_end = pos + 6;
|
||||
if (name_end >= xml.size() || !(xml[name_end] == ' ' || xml[name_end] == '\t' ||
|
||||
xml[name_end] == '\r' || xml[name_end] == '\n')) {
|
||||
pos = name_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto tag_end = xml.find('>', name_end);
|
||||
if (tag_end == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t chunk_id = 0;
|
||||
if (ParseXmlUintAttr(xml.substr(pos, tag_end - pos), "id", chunk_id)) {
|
||||
max_chunk_id = std::max(max_chunk_id, chunk_id);
|
||||
found = true;
|
||||
}
|
||||
|
||||
pos = tag_end + 1;
|
||||
}
|
||||
|
||||
if (!found || max_chunk_id >= 1000) {
|
||||
LOGF("invalid playgo chunk definitions: max_chunk_id=%" PRIu32 ", path=%s\n", max_chunk_id,
|
||||
Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
m_chunks_num = static_cast<uint16_t>(max_chunk_id + 1);
|
||||
m_opened = true;
|
||||
}
|
||||
|
||||
void PlayGo::OpenChunkManifest(const std::filesystem::path& file_name) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (m_opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::File f;
|
||||
f.Open(file_name, Common::File::Mode::Read);
|
||||
|
||||
if (f.IsInvalid()) {
|
||||
LOGF("Can't open %s\n", Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto buf = f.ReadWholeBuffer();
|
||||
f.Close();
|
||||
|
||||
constexpr uint8_t id_field[] = {0x08, 'i', 'd', 0x00};
|
||||
bool chunks[1000] {};
|
||||
uint32_t max_chunk_id = 0;
|
||||
bool found = false;
|
||||
const auto* data = buf.GetDataConst();
|
||||
|
||||
for (size_t pos = 0; pos + sizeof(id_field) + sizeof(uint32_t) <= buf.Size(); pos++) {
|
||||
if (std::memcmp(data + pos, id_field, sizeof(id_field)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* value = data + pos + sizeof(id_field);
|
||||
const auto chunk_id =
|
||||
static_cast<uint32_t>(value[0]) | (static_cast<uint32_t>(value[1]) << 8u) |
|
||||
(static_cast<uint32_t>(value[2]) << 16u) | (static_cast<uint32_t>(value[3]) << 24u);
|
||||
if (chunk_id < 1000) {
|
||||
chunks[chunk_id] = true;
|
||||
max_chunk_id = std::max(max_chunk_id, chunk_id);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found ||
|
||||
!std::all_of(chunks, chunks + max_chunk_id + 1, [](bool chunk) { return chunk; })) {
|
||||
LOGF("invalid chunk manifest: path=%s\n", Common::PathToString(file_name).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
m_chunks_num = static_cast<uint16_t>(max_chunk_id + 1);
|
||||
m_opened = true;
|
||||
LOGF("PlayGo: chunks num = %" PRIu16 " (from %s)\n", m_chunks_num,
|
||||
Common::PathToString(file_name).c_str());
|
||||
}
|
||||
|
||||
void PlayGo::Close() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (!m_f.IsInvalid()) {
|
||||
m_f.Close();
|
||||
}
|
||||
m_opened = false;
|
||||
m_chunks_num = 0;
|
||||
}
|
||||
|
||||
bool PlayGo::IsValid() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
return m_opened;
|
||||
}
|
||||
|
||||
bool PlayGo::GetChunksNum(uint32_t* num) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (m_opened) {
|
||||
*num = m_chunks_num;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlayGo::DbgPrint() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (!m_opened) {
|
||||
return;
|
||||
}
|
||||
LOGF("PlayGo: chunks num = %" PRIu16 "\n", m_chunks_num);
|
||||
}
|
||||
|
||||
void SystemContentLoadParamSfo(const std::filesystem::path& file_name) {
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
if (!Common::File::IsFileExisting(file_name)) {
|
||||
EXIT("Can't find file: %s\n", Common::PathToString(file_name).c_str());
|
||||
}
|
||||
|
||||
sc->psf.Open(file_name);
|
||||
|
||||
if (!sc->psf.IsValid()) {
|
||||
EXIT("invalid file: %s\n", Common::PathToString(file_name).c_str());
|
||||
}
|
||||
|
||||
sc->psf_path = file_name;
|
||||
sc->psf.DbgPrint();
|
||||
|
||||
sc->icon_path = file_name.parent_path() / "icon0.png";
|
||||
|
||||
if (!Common::File::IsFileExisting(sc->icon_path)) {
|
||||
sc->icon_path.clear();
|
||||
}
|
||||
|
||||
sc->playgo_path = file_name.parent_path() / "playgo-chunk.dat";
|
||||
sc->playgo.Open(sc->playgo_path);
|
||||
if (!sc->playgo.IsValid()) {
|
||||
sc->playgo_path = file_name.parent_path().parent_path() / "playgo-chunkdefs.xml";
|
||||
sc->playgo.OpenChunkDefs(sc->playgo_path);
|
||||
}
|
||||
if (!sc->playgo.IsValid()) {
|
||||
sc->playgo_path = file_name.parent_path().parent_path() / "Data" / "chunkmanifest";
|
||||
sc->playgo.OpenChunkManifest(sc->playgo_path);
|
||||
}
|
||||
|
||||
if (sc->playgo.IsValid()) {
|
||||
sc->playgo.DbgPrint();
|
||||
}
|
||||
}
|
||||
|
||||
bool SystemContentParamSfoGetInt(const char* name, int32_t* value) {
|
||||
if (name == nullptr || value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->psf.GetParamInt(name, value);
|
||||
}
|
||||
|
||||
bool SystemContentParamSfoGetString(const char* name, std::string* value) {
|
||||
if (name == nullptr || value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->psf.GetParamString(name, value);
|
||||
}
|
||||
|
||||
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size) {
|
||||
if (name == nullptr || value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->psf.GetParamString(name, value, value_size);
|
||||
}
|
||||
|
||||
uint64_t SystemContentGetFlexibleMemorySize() {
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->psf.GetFlexibleMemorySize();
|
||||
}
|
||||
|
||||
bool SystemContentGetIconPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
if (sc->icon_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*path = Common::PathToString(sc->icon_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemContentGetChunksNum(uint32_t* num) {
|
||||
if (num == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->playgo.GetChunksNum(num);
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_SYSTEMCONTENT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LOADER_SYSTEMCONTENT_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Loader {
|
||||
|
||||
void SystemContentLoadParamSfo(const std::filesystem::path& file_name);
|
||||
bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, std::string* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
|
||||
uint64_t SystemContentGetFlexibleMemorySize();
|
||||
bool SystemContentGetIconPath(std::string* path);
|
||||
bool SystemContentGetChunksNum(uint32_t* num);
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_LOADER_SYSTEMCONTENT_H_ */
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/dateTime.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "loader/timer.h"
|
||||
|
||||
namespace Loader::Timer {
|
||||
|
||||
static Common::Timer g_timer;
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Timer) {
|
||||
Start();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Timer) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Timer) {}
|
||||
|
||||
void Start() {
|
||||
g_timer.Start();
|
||||
}
|
||||
|
||||
double GetTimeMs() {
|
||||
return g_timer.GetTimeMs();
|
||||
}
|
||||
|
||||
Common::Time GetTime() {
|
||||
return Common::Time(static_cast<int>(GetTimeMs()));
|
||||
}
|
||||
|
||||
uint64_t GetCounter() {
|
||||
return g_timer.GetTicks();
|
||||
}
|
||||
|
||||
uint64_t GetFrequency() {
|
||||
return g_timer.GetFrequency();
|
||||
}
|
||||
|
||||
} // namespace Loader::Timer
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_LOADER_TIMER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_LOADER_TIMER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
namespace Loader::Timer {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Timer);
|
||||
|
||||
void Start();
|
||||
double GetTimeMs();
|
||||
Common::Time GetTime();
|
||||
uint64_t GetCounter();
|
||||
uint64_t GetFrequency();
|
||||
|
||||
} // namespace Loader::Timer
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_LOADER_TIMER_H_ */
|
||||
@@ -0,0 +1,155 @@
|
||||
#include "loader/x64InstructionEmulator.h"
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#endif
|
||||
|
||||
namespace Loader::X64InstructionEmulator {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
static M128A* GetContextXmm(PCONTEXT context, uint8_t index) {
|
||||
if (context == nullptr || index >= 16) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &context->Xmm0 + index;
|
||||
}
|
||||
|
||||
static uint64_t ExtractBitField(uint64_t value, uint32_t length, uint32_t index) {
|
||||
length &= 0x3fu;
|
||||
index &= 0x3fu;
|
||||
|
||||
if (length == 0) {
|
||||
length = 64;
|
||||
}
|
||||
|
||||
if (index >= 64) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto available = 64u - index;
|
||||
if (length > available) {
|
||||
length = available;
|
||||
}
|
||||
|
||||
const uint64_t mask = (length == 64 ? UINT64_MAX : ((uint64_t {1} << length) - 1u));
|
||||
return (value >> index) & mask;
|
||||
}
|
||||
|
||||
static uint64_t InsertBitField(uint64_t dst, uint64_t src, uint32_t length, uint32_t index) {
|
||||
length &= 0x3fu;
|
||||
index &= 0x3fu;
|
||||
|
||||
if (length == 0) {
|
||||
length = 64;
|
||||
}
|
||||
|
||||
if (index >= 64) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
auto available = 64u - index;
|
||||
if (length > available) {
|
||||
length = available;
|
||||
}
|
||||
|
||||
const uint64_t mask = (length == 64 ? UINT64_MAX : ((uint64_t {1} << length) - 1u));
|
||||
const uint64_t shifted = (index == 0 ? mask : (mask << index));
|
||||
const uint64_t src_shifted = (src & mask) << index;
|
||||
|
||||
return (dst & ~shifted) | src_shifted;
|
||||
}
|
||||
|
||||
static bool TryEmulateSse4a(PCONTEXT context) {
|
||||
if (context == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* rip = reinterpret_cast<const uint8_t*>(context->Rip);
|
||||
|
||||
const uint8_t prefix = rip[0];
|
||||
if (prefix != 0x66 && prefix != 0xf2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = 1;
|
||||
uint8_t rex = 0;
|
||||
if ((rip[offset] & 0xf0u) == 0x40u) {
|
||||
rex = rip[offset];
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (rip[offset] != 0x0f || rip[offset + 1] != 0x78) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto modrm = rip[offset + 2];
|
||||
if ((modrm & 0xc0u) != 0xc0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
|
||||
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
|
||||
const uint8_t length = rip[offset + 3];
|
||||
const uint8_t index = rip[offset + 4];
|
||||
|
||||
// AMD SSE4a immediate-form EXTRQ/INSERTQ. PS5 code can execute these natively on AMD hardware,
|
||||
// while Intel hosts raise an illegal-instruction exception.
|
||||
if (prefix == 0x66) {
|
||||
auto* dst = GetContextXmm(context, rm);
|
||||
if (dst == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dst->Low = ExtractBitField(dst->Low, length, index);
|
||||
dst->High = 0;
|
||||
context->Rip += offset + 5;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* dst = GetContextXmm(context, reg);
|
||||
auto* src = GetContextXmm(context, rm);
|
||||
if (dst == nullptr || src == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dst->Low = InsertBitField(dst->Low, src->Low, length, index);
|
||||
context->Rip += offset + 5;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryEmulateMonitorxMwaitx(PCONTEXT context) {
|
||||
if (context == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* rip = reinterpret_cast<const uint8_t*>(context->Rip);
|
||||
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// AMD MONITORX/MWAITX are used by PS5 code in wait loops. Intel hosts can raise an illegal-
|
||||
// instruction exception, so approximate them as a no-op/yield pair.
|
||||
if (rip[2] == 0xfb) {
|
||||
SwitchToThread();
|
||||
}
|
||||
context->Rip += 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool TryEmulate(void* native_context) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
auto* context = static_cast<PCONTEXT>(native_context);
|
||||
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context);
|
||||
#else
|
||||
(void)native_context;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Loader::X64InstructionEmulator
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
|
||||
#define KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_
|
||||
|
||||
namespace Loader::X64InstructionEmulator {
|
||||
|
||||
[[nodiscard]] bool TryEmulate(void* native_context);
|
||||
|
||||
} // namespace Loader::X64InstructionEmulator
|
||||
|
||||
#endif /* KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_ */
|
||||
Reference in New Issue
Block a user