mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-24 12:25:00 +00:00
core renaming
This commit is contained in:
parent
aee25dcaf9
commit
53a8024e43
54 changed files with 0 additions and 0 deletions
93
src/core/FsFile.cpp
Normal file
93
src/core/FsFile.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "FsFile.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
File::File() = default;
|
||||
|
||||
File::File(const std::string& path, OpenMode mode) {
|
||||
open(path, mode);
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool File::open(const std::string& path, OpenMode mode) {
|
||||
close();
|
||||
#ifdef _WIN64
|
||||
fopen_s(&m_file, path.c_str(), getOpenMode(mode));
|
||||
#else
|
||||
m_file = std::fopen(path.c_str(), getOpenMode(mode));
|
||||
#endif
|
||||
return isOpen();
|
||||
}
|
||||
|
||||
bool File::close() {
|
||||
if (!isOpen() || std::fclose(m_file) != 0) [[unlikely]] {
|
||||
m_file = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::write(std::span<const u08> data) {
|
||||
return isOpen() && std::fwrite(data.data(), 1, data.size(), m_file) == data.size();
|
||||
}
|
||||
|
||||
bool File::read(void* data, u64 size) const {
|
||||
return isOpen() && std::fread(data, 1, size, m_file) == size;
|
||||
}
|
||||
|
||||
bool File::seek(s64 offset, SeekMode mode) {
|
||||
#ifdef _WIN64
|
||||
if (!isOpen() || _fseeki64(m_file, offset, getSeekMode(mode)) != 0) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!isOpen() || fseeko64(m_file, offset, getSeekMode(mode)) != 0) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 File::tell() const {
|
||||
if (isOpen()) [[likely]] {
|
||||
#ifdef _WIN64
|
||||
return _ftelli64(m_file);
|
||||
#else
|
||||
return ftello64(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
u64 File::getFileSize() {
|
||||
#ifdef _WIN64
|
||||
const u64 pos = _ftelli64(m_file);
|
||||
if (_fseeki64(m_file, 0, SEEK_END) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u64 size = _ftelli64(m_file);
|
||||
if (_fseeki64(m_file, pos, SEEK_SET) != 0) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
const u64 pos = ftello64(m_file);
|
||||
if (fseeko64(m_file, 0, SEEK_END) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u64 size = ftello64(m_file);
|
||||
if (fseeko64(m_file, pos, SEEK_SET) != 0) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
87
src/core/FsFile.h
Normal file
87
src/core/FsFile.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "../types.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
enum class OpenMode : u32 {
|
||||
Read = 0x1,
|
||||
Write = 0x2,
|
||||
ReadWrite = Read | Write
|
||||
};
|
||||
|
||||
enum class SeekMode : u32 {
|
||||
Set,
|
||||
Cur,
|
||||
End,
|
||||
};
|
||||
|
||||
class File {
|
||||
public:
|
||||
File();
|
||||
explicit File(const std::string& path, OpenMode mode = OpenMode::Read);
|
||||
~File();
|
||||
|
||||
bool open(const std::string& path, OpenMode mode = OpenMode::Read);
|
||||
bool close();
|
||||
bool read(void* data, u64 size) const;
|
||||
bool write(std::span<const u08> data);
|
||||
bool seek(s64 offset, SeekMode mode);
|
||||
u64 getFileSize();
|
||||
u64 tell() const;
|
||||
|
||||
template <typename T>
|
||||
bool read(T& data) const {
|
||||
return read(&data, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool read(std::vector<T>& data) const {
|
||||
return read(data.data(), data.size() * sizeof(T));
|
||||
}
|
||||
|
||||
bool isOpen() const {
|
||||
return m_file != nullptr;
|
||||
}
|
||||
|
||||
const char* getOpenMode(OpenMode mode) const {
|
||||
switch (mode) {
|
||||
case OpenMode::Read:
|
||||
return "rb";
|
||||
case OpenMode::Write:
|
||||
return "wb";
|
||||
case OpenMode::ReadWrite:
|
||||
return "r+b";
|
||||
default:
|
||||
return "r";
|
||||
}
|
||||
}
|
||||
|
||||
int getSeekMode(SeekMode mode) const {
|
||||
switch (mode) {
|
||||
case SeekMode::Set:
|
||||
return SEEK_SET;
|
||||
case SeekMode::Cur:
|
||||
return SEEK_CUR;
|
||||
case SeekMode::End:
|
||||
return SEEK_END;
|
||||
default:
|
||||
return SEEK_SET;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::FILE* fileDescr() const {
|
||||
return m_file;
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* m_file{};
|
||||
};
|
||||
|
||||
} // namespace Common::FS
|
188
src/core/PS4/GPU/gpu_memory.cpp
Normal file
188
src/core/PS4/GPU/gpu_memory.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include "gpu_memory.h"
|
||||
|
||||
#include <xxhash/xxh3.h>
|
||||
|
||||
#include "Emulator/Util/singleton.h"
|
||||
|
||||
void* GPU::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, void* todo /*CommandBuffer?*/, u64 virtual_addr, u64 size,
|
||||
const GPUObject& info) {
|
||||
auto* gpumemory = singleton<GPUMemory>::instance();
|
||||
|
||||
return gpumemory->memoryCreateObj(submit_id, ctx, nullptr, &virtual_addr, &size, 1, info);
|
||||
}
|
||||
|
||||
void GPU::memorySetAllocArea(u64 virtual_addr, u64 size) {
|
||||
auto* gpumemory = singleton<GPUMemory>::instance();
|
||||
|
||||
std::scoped_lock lock{gpumemory->m_mutex};
|
||||
|
||||
MemoryHeap h;
|
||||
h.allocated_virtual_addr = virtual_addr;
|
||||
h.allocated_size = size;
|
||||
|
||||
gpumemory->m_heaps.push_back(h);
|
||||
}
|
||||
|
||||
u64 GPU::calculate_hash(const u08* buf, u64 size) { return (size > 0 && buf != nullptr ? XXH3_64bits(buf, size) : 0); }
|
||||
|
||||
bool GPU::vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanMemory* mem) {
|
||||
static std::atomic_uint64_t unique_id = 0;
|
||||
|
||||
VkPhysicalDeviceMemoryProperties memory_properties{};
|
||||
vkGetPhysicalDeviceMemoryProperties(ctx->m_physical_device, &memory_properties);
|
||||
|
||||
u32 index = 0;
|
||||
for (; index < memory_properties.memoryTypeCount; index++) {
|
||||
if ((mem->requirements.memoryTypeBits & (static_cast<uint32_t>(1) << index)) != 0 &&
|
||||
(memory_properties.memoryTypes[index].propertyFlags & mem->property) == mem->property) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mem->type = index;
|
||||
mem->offset = 0;
|
||||
|
||||
VkMemoryAllocateInfo alloc_info{};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.pNext = nullptr;
|
||||
alloc_info.allocationSize = mem->requirements.size;
|
||||
alloc_info.memoryTypeIndex = index;
|
||||
|
||||
mem->unique_id = ++unique_id;
|
||||
|
||||
auto result = vkAllocateMemory(ctx->m_device, &alloc_info, nullptr, &mem->memory);
|
||||
|
||||
if (result == VK_SUCCESS) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GPU::flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx) {
|
||||
auto* gpumemory = singleton<GPUMemory>::instance();
|
||||
gpumemory->flushAllHeaps(ctx);
|
||||
}
|
||||
|
||||
int GPU::GPUMemory::getHeapId(u64 virtual_addr, u64 size) {
|
||||
int index = 0;
|
||||
for (const auto& heap : m_heaps) {
|
||||
if ((virtual_addr >= heap.allocated_virtual_addr && virtual_addr < heap.allocated_virtual_addr + heap.allocated_size) ||
|
||||
((virtual_addr + size - 1) >= heap.allocated_virtual_addr &&
|
||||
(virtual_addr + size - 1) < heap.allocated_virtual_addr + heap.allocated_size)) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* GPU::GPUMemory::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, void* todo, const u64* virtual_addr, const u64* size,
|
||||
int virtual_addr_num, const GPUObject& info) {
|
||||
auto* gpumemory = singleton<GPUMemory>::instance();
|
||||
|
||||
std::scoped_lock lock{gpumemory->m_mutex};
|
||||
|
||||
int heap_id = gpumemory->getHeapId(virtual_addr[0], size[0]);
|
||||
|
||||
if (heap_id < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& heap = m_heaps[heap_id];
|
||||
|
||||
ObjInfo objInfo = {};
|
||||
|
||||
// copy parameters from info to obj
|
||||
for (int i = 0; i < 8; i++) {
|
||||
objInfo.obj_params[i] = info.obj_params[i];
|
||||
}
|
||||
|
||||
objInfo.gpu_object.objectType = info.objectType;
|
||||
objInfo.gpu_object.obj = nullptr;
|
||||
|
||||
for (int h = 0; h < virtual_addr_num; h++) {
|
||||
if (info.check_hash) {
|
||||
objInfo.hash[h] = GPU::calculate_hash(reinterpret_cast<const u08*>(virtual_addr[h]), size[h]);
|
||||
} else {
|
||||
objInfo.hash[h] = 0;
|
||||
}
|
||||
}
|
||||
objInfo.submit_id = submit_id;
|
||||
objInfo.check_hash = info.check_hash;
|
||||
|
||||
objInfo.gpu_object.obj = info.getCreateFunc()(ctx, objInfo.obj_params, virtual_addr, size, virtual_addr_num, &objInfo.mem);
|
||||
|
||||
objInfo.update_func = info.getUpdateFunc();
|
||||
int index = static_cast<int>(heap.objects.size());
|
||||
|
||||
HeapObject hobj{};
|
||||
hobj.block = createHeapBlock(virtual_addr, size, virtual_addr_num, heap_id, index);
|
||||
hobj.info = objInfo;
|
||||
hobj.free = false;
|
||||
heap.objects.push_back(hobj);
|
||||
|
||||
return objInfo.gpu_object.obj;
|
||||
}
|
||||
|
||||
GPU::HeapBlock GPU::GPUMemory::createHeapBlock(const u64* virtual_addr, const u64* size, int virtual_addr_num, int heap_id, int obj_id) {
|
||||
auto& heap = m_heaps[heap_id];
|
||||
|
||||
GPU::HeapBlock heapBlock{};
|
||||
heapBlock.virtual_addr_num = virtual_addr_num;
|
||||
for (int vi = 0; vi < virtual_addr_num; vi++) {
|
||||
heapBlock.virtual_addr[vi] = virtual_addr[vi];
|
||||
heapBlock.size[vi] = size[vi];
|
||||
}
|
||||
return heapBlock;
|
||||
}
|
||||
|
||||
void GPU::GPUMemory::update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, int obj_id) {
|
||||
auto& heap = m_heaps[heap_id];
|
||||
|
||||
auto& heapObj = heap.objects[obj_id];
|
||||
auto& objInfo = heapObj.info;
|
||||
bool need_update = false;
|
||||
|
||||
if (submit_id > objInfo.submit_id) {
|
||||
uint64_t hash[3] = {};
|
||||
|
||||
for (int i = 0; i < heapObj.block.virtual_addr_num; i++) {
|
||||
if (objInfo.check_hash) {
|
||||
hash[i] = GPU::calculate_hash(reinterpret_cast<const uint8_t*>(heapObj.block.virtual_addr[i]), heapObj.block.size[i]);
|
||||
} else {
|
||||
hash[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < heapObj.block.virtual_addr_num; i++) {
|
||||
if (objInfo.hash[i] != hash[i]) {
|
||||
need_update = true;
|
||||
objInfo.hash[i] = hash[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (submit_id != UINT64_MAX) {
|
||||
objInfo.submit_id = submit_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_update) {
|
||||
objInfo.update_func(ctx, objInfo.obj_params, objInfo.gpu_object.obj, heapObj.block.virtual_addr, heapObj.block.size,
|
||||
heapObj.block.virtual_addr_num);
|
||||
}
|
||||
}
|
||||
|
||||
void GPU::GPUMemory::flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
int heap_id = 0;
|
||||
for (auto& heap : m_heaps) {
|
||||
int index = 0;
|
||||
for (auto& heapObj : heap.objects) {
|
||||
if (!heapObj.free) {
|
||||
update(UINT64_MAX, ctx, heap_id, index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
heap_id++;
|
||||
}
|
||||
}
|
85
src/core/PS4/GPU/gpu_memory.h
Normal file
85
src/core/PS4/GPU/gpu_memory.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <core/PS4/HLE/Graphics/graphics_ctx.h>
|
||||
#include <types.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace GPU {
|
||||
|
||||
class GPUObject;
|
||||
|
||||
enum class MemoryMode : u32 { NoAccess = 0, Read = 1, Write = 2, ReadWrite = 3 };
|
||||
enum class MemoryObjectType : u64 { InvalidObj, VideoOutBufferObj };
|
||||
|
||||
struct GpuMemoryObject {
|
||||
MemoryObjectType objectType = MemoryObjectType::InvalidObj;
|
||||
void* obj = nullptr;
|
||||
};
|
||||
|
||||
struct HeapBlock {
|
||||
u64 virtual_addr[3] = {};
|
||||
u64 size[3] = {};
|
||||
int virtual_addr_num = 0;
|
||||
};
|
||||
|
||||
class GPUObject {
|
||||
public:
|
||||
GPUObject() = default;
|
||||
virtual ~GPUObject() = default;
|
||||
u64 obj_params[8] = {};
|
||||
bool check_hash = false;
|
||||
bool isReadOnly = false;
|
||||
MemoryObjectType objectType = MemoryObjectType::InvalidObj;
|
||||
|
||||
using create_func_t = void* (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, const u64* virtual_addr, const u64* size, int virtual_addr_num,
|
||||
HLE::Libs::Graphics::VulkanMemory* mem);
|
||||
using update_func_t = void (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, void* obj, const u64* virtual_addr, const u64* size,
|
||||
int virtual_addr_num);
|
||||
|
||||
virtual create_func_t getCreateFunc() const = 0;
|
||||
virtual update_func_t getUpdateFunc() const = 0;
|
||||
};
|
||||
|
||||
struct ObjInfo {
|
||||
u64 obj_params[8] = {};
|
||||
GpuMemoryObject gpu_object;
|
||||
u64 hash[3] = {};
|
||||
u64 submit_id = 0;
|
||||
bool check_hash = false;
|
||||
HLE::Libs::Graphics::VulkanMemory mem;
|
||||
GPU::GPUObject::update_func_t update_func = nullptr;
|
||||
};
|
||||
|
||||
struct HeapObject {
|
||||
HeapBlock block;
|
||||
ObjInfo info;
|
||||
bool free = true;
|
||||
};
|
||||
struct MemoryHeap {
|
||||
u64 allocated_virtual_addr = 0;
|
||||
u64 allocated_size = 0;
|
||||
std::vector<HeapObject> objects;
|
||||
};
|
||||
|
||||
class GPUMemory {
|
||||
public:
|
||||
GPUMemory() {}
|
||||
virtual ~GPUMemory() {}
|
||||
int getHeapId(u64 vaddr, u64 size);
|
||||
std::mutex m_mutex;
|
||||
std::vector<MemoryHeap> m_heaps;
|
||||
void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, /*CommandBuffer* buffer*/ void* todo, const u64* virtual_addr,
|
||||
const u64* size, int virtual_addr_num, const GPUObject& info);
|
||||
HeapBlock createHeapBlock(const u64* virtual_addr, const u64* size, int virtual_addr_num, int heap_id, int obj_id);
|
||||
void update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, int obj_id);
|
||||
void flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx);
|
||||
};
|
||||
|
||||
void memorySetAllocArea(u64 virtual_addr, u64 size);
|
||||
void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, /*CommandBuffer* buffer*/ void* todo, u64 virtual_addr, u64 size,
|
||||
const GPUObject& info);
|
||||
u64 calculate_hash(const u08* buf, u64 size);
|
||||
bool vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanMemory* mem);
|
||||
void flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx);
|
||||
} // namespace GPU
|
169
src/core/PS4/GPU/tile_manager.cpp
Normal file
169
src/core/PS4/GPU/tile_manager.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
#include "tile_manager.h"
|
||||
#include "Emulator/Util/singleton.h"
|
||||
#include <mutex>
|
||||
|
||||
namespace GPU {
|
||||
|
||||
static u32 IntLog2(u32 i) { return 31 - __builtin_clz(i | 1u); }
|
||||
|
||||
class TileManager {
|
||||
public:
|
||||
TileManager(){}
|
||||
virtual ~TileManager() { }
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
class TileManager32 {
|
||||
public:
|
||||
u32 m_macro_tile_height = 0;
|
||||
u32 m_bank_height = 0;
|
||||
u32 m_num_banks = 0;
|
||||
u32 m_num_pipes = 0;
|
||||
u32 m_padded_width = 0;
|
||||
u32 m_padded_height = 0;
|
||||
u32 m_pipe_bits = 0;
|
||||
u32 m_bank_bits = 0;
|
||||
|
||||
void Init(u32 width, u32 height, bool is_neo) {
|
||||
m_macro_tile_height = (is_neo ? 128 : 64);
|
||||
m_bank_height = is_neo ? 2 : 1;
|
||||
m_num_banks = is_neo ? 8 : 16;
|
||||
m_num_pipes = is_neo ? 16 : 8;
|
||||
m_padded_width = width;
|
||||
if (height == 1080) {
|
||||
m_padded_height = is_neo ? 1152 : 1088;
|
||||
}
|
||||
if (height == 720) {
|
||||
m_padded_height = 768;
|
||||
}
|
||||
m_pipe_bits = is_neo ? 4 : 3;
|
||||
m_bank_bits = is_neo ? 3 : 4;
|
||||
}
|
||||
|
||||
static u32 getElementIdx(u32 x, u32 y) {
|
||||
u32 elem = 0;
|
||||
elem |= ((x >> 0u) & 0x1u) << 0u;
|
||||
elem |= ((x >> 1u) & 0x1u) << 1u;
|
||||
elem |= ((y >> 0u) & 0x1u) << 2u;
|
||||
elem |= ((x >> 2u) & 0x1u) << 3u;
|
||||
elem |= ((y >> 1u) & 0x1u) << 4u;
|
||||
elem |= ((y >> 2u) & 0x1u) << 5u;
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
static u32 getPipeIdx(u32 x, u32 y, bool is_neo) {
|
||||
u32 pipe = 0;
|
||||
|
||||
if (!is_neo) {
|
||||
pipe |= (((x >> 3u) ^ (y >> 3u) ^ (x >> 4u)) & 0x1u) << 0u;
|
||||
pipe |= (((x >> 4u) ^ (y >> 4u)) & 0x1u) << 1u;
|
||||
pipe |= (((x >> 5u) ^ (y >> 5u)) & 0x1u) << 2u;
|
||||
} else {
|
||||
pipe |= (((x >> 3u) ^ (y >> 3u) ^ (x >> 4u)) & 0x1u) << 0u;
|
||||
pipe |= (((x >> 4u) ^ (y >> 4u)) & 0x1u) << 1u;
|
||||
pipe |= (((x >> 5u) ^ (y >> 5u)) & 0x1u) << 2u;
|
||||
pipe |= (((x >> 6u) ^ (y >> 5u)) & 0x1u) << 3u;
|
||||
}
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
static u32 getBankIdx(u32 x, u32 y, u32 bank_width, u32 bank_height, u32 num_banks, u32 num_pipes) {
|
||||
const u32 x_shift_offset = IntLog2(bank_width * num_pipes);
|
||||
const u32 y_shift_offset = IntLog2(bank_height);
|
||||
const u32 xs = x >> x_shift_offset;
|
||||
const u32 ys = y >> y_shift_offset;
|
||||
u32 bank = 0;
|
||||
switch (num_banks) {
|
||||
case 8:
|
||||
bank |= (((xs >> 3u) ^ (ys >> 5u)) & 0x1u) << 0u;
|
||||
bank |= (((xs >> 4u) ^ (ys >> 4u) ^ (ys >> 5u)) & 0x1u) << 1u;
|
||||
bank |= (((xs >> 5u) ^ (ys >> 3u)) & 0x1u) << 2u;
|
||||
break;
|
||||
case 16:
|
||||
bank |= (((xs >> 3u) ^ (ys >> 6u)) & 0x1u) << 0u;
|
||||
bank |= (((xs >> 4u) ^ (ys >> 5u) ^ (ys >> 6u)) & 0x1u) << 1u;
|
||||
bank |= (((xs >> 5u) ^ (ys >> 4u)) & 0x1u) << 2u;
|
||||
bank |= (((xs >> 6u) ^ (ys >> 3u)) & 0x1u) << 3u;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
||||
return bank;
|
||||
}
|
||||
|
||||
u64 getTiledOffs(u32 x, u32 y, bool is_neo) const {
|
||||
u64 element_index = getElementIdx(x, y);
|
||||
|
||||
u32 xh = x;
|
||||
u32 yh = y;
|
||||
u64 pipe = getPipeIdx(xh, yh, is_neo);
|
||||
u64 bank = getBankIdx(xh, yh, 1, m_bank_height, m_num_banks, m_num_pipes);
|
||||
u32 tile_bytes = (8 * 8 * 32 + 7) / 8;
|
||||
u64 element_offset = (element_index * 32);
|
||||
u64 tile_split_slice = 0;
|
||||
|
||||
if (tile_bytes > 512) {
|
||||
tile_split_slice = element_offset / (static_cast<u64>(512) * 8);
|
||||
element_offset %= (static_cast<u64>(512) * 8);
|
||||
tile_bytes = 512;
|
||||
}
|
||||
|
||||
u64 macro_tile_bytes = (128 / 8) * (m_macro_tile_height / 8) * tile_bytes / (m_num_pipes * m_num_banks);
|
||||
u64 macro_tiles_per_row = m_padded_width / 128;
|
||||
u64 macro_tile_row_index = y / m_macro_tile_height;
|
||||
u64 macro_tile_column_index = x / 128;
|
||||
u64 macro_tile_index = (macro_tile_row_index * macro_tiles_per_row) + macro_tile_column_index;
|
||||
u64 macro_tile_offset = macro_tile_index * macro_tile_bytes;
|
||||
u64 macro_tiles_per_slice = macro_tiles_per_row * (m_padded_height / m_macro_tile_height);
|
||||
u64 slice_bytes = macro_tiles_per_slice * macro_tile_bytes;
|
||||
u64 slice_offset = tile_split_slice * slice_bytes;
|
||||
u64 tile_row_index = (y / 8) % m_bank_height;
|
||||
u64 tile_index = tile_row_index;
|
||||
u64 tile_offset = tile_index * tile_bytes;
|
||||
|
||||
u64 tile_split_slice_rotation = ((m_num_banks / 2) + 1) * tile_split_slice;
|
||||
bank ^= tile_split_slice_rotation;
|
||||
bank &= (m_num_banks - 1);
|
||||
|
||||
u64 total_offset = (slice_offset + macro_tile_offset + tile_offset) * 8 + element_offset;
|
||||
u64 bit_offset = total_offset & 0x7u;
|
||||
total_offset /= 8;
|
||||
|
||||
u64 pipe_interleave_offset = total_offset & 0xffu;
|
||||
u64 offset = total_offset >> 8u;
|
||||
u64 byte_offset = pipe_interleave_offset | (pipe << (8u)) | (bank << (8u + m_pipe_bits)) | (offset << (8u + m_pipe_bits + m_bank_bits));
|
||||
|
||||
return ((byte_offset << 3u) | bit_offset) / 8;
|
||||
}
|
||||
};
|
||||
|
||||
void convertTileToLinear(void* dst, const void* src,u32 width, u32 height, bool is_neo) {
|
||||
TileManager32 t;
|
||||
t.Init(width, height, is_neo);
|
||||
|
||||
auto* g_TileManager = singleton<TileManager>::instance();
|
||||
|
||||
std::scoped_lock lock{g_TileManager->m_mutex};
|
||||
|
||||
for (u32 y = 0; y < height; y++) {
|
||||
u32 x = 0;
|
||||
u64 linear_offset = y * width * 4;
|
||||
|
||||
for (; x + 1 < width; x += 2) {
|
||||
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
|
||||
|
||||
*reinterpret_cast<u64*>(static_cast<u08*>(dst) + linear_offset) =
|
||||
*reinterpret_cast<const u64*>(static_cast<const u08*>(src) + tiled_offset);
|
||||
linear_offset += 8;
|
||||
}
|
||||
if (x < width) {
|
||||
auto tiled_offset = t.getTiledOffs(x, y, is_neo);
|
||||
|
||||
*reinterpret_cast<u32*>(static_cast<u08*>(dst) + linear_offset) =
|
||||
*reinterpret_cast<const u32*>(static_cast<const u08*>(src) + tiled_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace GPU
|
8
src/core/PS4/GPU/tile_manager.h
Normal file
8
src/core/PS4/GPU/tile_manager.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace GPU {
|
||||
|
||||
void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool neo);
|
||||
}
|
139
src/core/PS4/GPU/video_out_buffer.cpp
Normal file
139
src/core/PS4/GPU/video_out_buffer.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
#include "video_out_buffer.h"
|
||||
|
||||
#include <Util/log.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include <vulkan_util.h>
|
||||
#include "tile_manager.h"
|
||||
|
||||
constexpr bool log_file_videoOutBuffer = true; // disable it to disable logging
|
||||
|
||||
static void update_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, void* obj, const u64* virtual_addr, const u64* size,
|
||||
int virtual_addr_num) {
|
||||
|
||||
auto pitch = params[GPU::VideoOutBufferObj::PITCH_PARAM];
|
||||
bool tiled = (params[GPU::VideoOutBufferObj::IS_TILE_PARAM] != 0);
|
||||
bool neo = (params[GPU::VideoOutBufferObj::IS_NEO_PARAM] != 0);
|
||||
auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM];
|
||||
auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM];
|
||||
|
||||
auto* vk_obj = static_cast<HLE::Libs::Graphics::VideoOutVulkanImage*>(obj);
|
||||
|
||||
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
if (tiled)
|
||||
{
|
||||
std::vector<u08> tempbuff(*size);
|
||||
GPU::convertTileToLinear(tempbuff.data(), reinterpret_cast<void*>(*virtual_addr), width, height, neo);
|
||||
Graphics::Vulkan::vulkanFillImage(ctx, vk_obj, tempbuff.data(), *size, pitch, static_cast<u64>(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
|
||||
} else {
|
||||
Graphics::Vulkan::vulkanFillImage(ctx, vk_obj, reinterpret_cast<void*>(*virtual_addr), *size, pitch,
|
||||
static_cast<u64>(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, const u64* virtual_addr, const u64* size, int virtual_addr_num,
|
||||
HLE::Libs::Graphics::VulkanMemory* mem) {
|
||||
auto pixel_format = params[GPU::VideoOutBufferObj::PIXEL_FORMAT_PARAM];
|
||||
auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM];
|
||||
auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM];
|
||||
|
||||
auto* vk_obj = new HLE::Libs::Graphics::VideoOutVulkanImage;
|
||||
|
||||
VkFormat vk_format = VK_FORMAT_UNDEFINED;
|
||||
|
||||
switch (pixel_format) {
|
||||
case static_cast<uint64_t>(GPU::VideoOutBufferFormat::R8G8B8A8Srgb): vk_format = VK_FORMAT_R8G8B8A8_SRGB; break;
|
||||
case static_cast<uint64_t>(GPU::VideoOutBufferFormat::B8G8R8A8Srgb): vk_format = VK_FORMAT_B8G8R8A8_SRGB; break;
|
||||
default: LOG_CRITICAL_IF(log_file_videoOutBuffer, "unknown pixelFormat = {}\n", pixel_format); std::exit(0);
|
||||
}
|
||||
|
||||
vk_obj->extent.width = width;
|
||||
vk_obj->extent.height = height;
|
||||
vk_obj->format = vk_format;
|
||||
vk_obj->image = nullptr;
|
||||
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
for (auto& view : vk_obj->image_view) {
|
||||
view = nullptr;
|
||||
}
|
||||
|
||||
VkImageCreateInfo image_info{};
|
||||
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_info.pNext = nullptr;
|
||||
image_info.flags = 0;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.extent.width = vk_obj->extent.width;
|
||||
image_info.extent.height = vk_obj->extent.height;
|
||||
image_info.extent.depth = 1;
|
||||
image_info.mipLevels = 1;
|
||||
image_info.arrayLayers = 1;
|
||||
image_info.format = vk_obj->format;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.initialLayout = vk_obj->layout;
|
||||
image_info.usage =
|
||||
static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
vkCreateImage(ctx->m_device, &image_info, nullptr, &vk_obj->image);
|
||||
|
||||
if (vk_obj->image == nullptr) {
|
||||
LOG_CRITICAL_IF(log_file_videoOutBuffer, "vk_obj->image is null\n");
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
vkGetImageMemoryRequirements(ctx->m_device, vk_obj->image, &mem->requirements);
|
||||
|
||||
mem->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
bool allocated = GPU::vulkanAllocateMemory(ctx, mem);
|
||||
|
||||
if (!allocated) {
|
||||
LOG_CRITICAL_IF(log_file_videoOutBuffer, "can't allocate vulkan memory\n");
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
vkBindImageMemory(ctx->m_device, vk_obj->image, mem->memory, mem->offset);
|
||||
|
||||
vk_obj->memory = *mem;
|
||||
|
||||
LOG_INFO_IF(log_file_videoOutBuffer, "videoOutBuffer create object\n");
|
||||
LOG_INFO_IF(log_file_videoOutBuffer, "mem requirements.size = {}\n", mem->requirements.size);
|
||||
LOG_INFO_IF(log_file_videoOutBuffer, "width = {}\n", width);
|
||||
LOG_INFO_IF(log_file_videoOutBuffer, "height = {}\n", height);
|
||||
LOG_INFO_IF(log_file_videoOutBuffer, "size = {}\n", *size);
|
||||
|
||||
update_func(ctx, params, vk_obj, virtual_addr, size, virtual_addr_num);
|
||||
|
||||
VkImageViewCreateInfo create_info{};
|
||||
create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
create_info.pNext = nullptr;
|
||||
create_info.flags = 0;
|
||||
create_info.image = vk_obj->image;
|
||||
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
create_info.format = vk_obj->format;
|
||||
create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
create_info.subresourceRange.baseArrayLayer = 0;
|
||||
create_info.subresourceRange.baseMipLevel = 0;
|
||||
create_info.subresourceRange.layerCount = 1;
|
||||
create_info.subresourceRange.levelCount = 1;
|
||||
|
||||
vkCreateImageView(ctx->m_device, &create_info, nullptr, &vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT]);
|
||||
|
||||
if (vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT] == nullptr) {
|
||||
LOG_CRITICAL_IF(log_file_videoOutBuffer, "vk_obj->image_view is null\n");
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
return vk_obj;
|
||||
}
|
||||
|
||||
GPU::GPUObject::create_func_t GPU::VideoOutBufferObj::getCreateFunc() const { return create_func; }
|
||||
|
||||
GPU::GPUObject::update_func_t GPU::VideoOutBufferObj::getUpdateFunc() const { return update_func; }
|
38
src/core/PS4/GPU/video_out_buffer.h
Normal file
38
src/core/PS4/GPU/video_out_buffer.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <types.h>
|
||||
|
||||
#include "gpu_memory.h"
|
||||
|
||||
namespace GPU {
|
||||
|
||||
enum class VideoOutBufferFormat : u64 {
|
||||
Unknown,
|
||||
R8G8B8A8Srgb,
|
||||
B8G8R8A8Srgb,
|
||||
};
|
||||
|
||||
class VideoOutBufferObj : public GPUObject {
|
||||
public:
|
||||
static constexpr int PIXEL_FORMAT_PARAM = 0;
|
||||
static constexpr int WIDTH_PARAM = 1;
|
||||
static constexpr int HEIGHT_PARAM = 2;
|
||||
static constexpr int IS_TILE_PARAM = 3;
|
||||
static constexpr int IS_NEO_PARAM = 4;
|
||||
static constexpr int PITCH_PARAM = 5;
|
||||
|
||||
explicit VideoOutBufferObj(VideoOutBufferFormat pixel_format, u32 width, u32 height, bool is_tiled, bool is_neo, u32 pitch) {
|
||||
obj_params[PIXEL_FORMAT_PARAM] = static_cast<uint64_t>(pixel_format);
|
||||
obj_params[WIDTH_PARAM] = width;
|
||||
obj_params[HEIGHT_PARAM] = height;
|
||||
obj_params[IS_TILE_PARAM] = is_tiled ? 1 : 0;
|
||||
obj_params[IS_NEO_PARAM] = is_neo ? 1 : 0;
|
||||
obj_params[PITCH_PARAM] = pitch;
|
||||
check_hash = true;
|
||||
objectType = GPU::MemoryObjectType::VideoOutBufferObj;
|
||||
}
|
||||
|
||||
create_func_t getCreateFunc() const override;
|
||||
update_func_t getUpdateFunc() const override;
|
||||
};
|
||||
} // namespace GPU
|
22
src/core/PS4/HLE/ErrorCodes.h
Normal file
22
src/core/PS4/HLE/ErrorCodes.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
constexpr int SCE_OK = 0;
|
||||
|
||||
constexpr int SCE_KERNEL_ERROR_EBADF = 0x80020009;
|
||||
constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000c; // Insufficient memory
|
||||
constexpr int SCE_KERNEL_ERROR_EFAULT = 0x8002000e; // Invalid address pointer
|
||||
constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; // null or invalid states
|
||||
constexpr int SCE_KERNEL_ERROR_EAGAIN = 0x80020023; // Memory cannot be allocated
|
||||
constexpr int SCE_KERNEL_ERROR_ENAMETOOLONG = 0x8002003f; // character strings exceeds valid size
|
||||
|
||||
// videoOut
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_VALUE = 0x80290001; // invalid argument
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS = 0x80290002; // invalid addresses
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE = 0x80290007; // invalid tiling mode
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO = 0x80290008; // invalid aspect ration
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY = 0x80290009; // already opened
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_INDEX = 0x8029000A; // invalid buffer index
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_HANDLE = 0x8029000B; // invalid handle
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE = 0x8029000C; // Invalid event queue
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED = 0x80290010; // slot already used
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL = 0x80290012; // flip queue is full
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_OPTION = 0x8029001A; // Invalid buffer attribute option
|
134
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp
Normal file
134
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#include "video_out_ctx.h"
|
||||
|
||||
#include <core/PS4/HLE/LibKernel.h>
|
||||
#include <debug.h>
|
||||
#include <core/hle/libraries/libkernel/time_management.h>
|
||||
|
||||
namespace HLE::Graphics::Objects {
|
||||
|
||||
void VideoOutCtx::Init(u32 width, u32 height) {
|
||||
m_video_out_ctx.m_resolution.fullWidth = width;
|
||||
m_video_out_ctx.m_resolution.fullHeight = height;
|
||||
m_video_out_ctx.m_resolution.paneWidth = width;
|
||||
m_video_out_ctx.m_resolution.paneHeight = height;
|
||||
}
|
||||
int VideoOutCtx::Open() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
int handle = -1;
|
||||
|
||||
if (!m_video_out_ctx.isOpened) {
|
||||
handle = 1; // positive return , should be more than 1 ?
|
||||
}
|
||||
|
||||
m_video_out_ctx.isOpened = true;
|
||||
m_video_out_ctx.m_flip_status = SceVideoOutFlipStatus();
|
||||
m_video_out_ctx.m_flip_status.flipArg = -1;
|
||||
m_video_out_ctx.m_flip_status.currentBuffer = -1;
|
||||
m_video_out_ctx.m_flip_status.count = 0;
|
||||
m_video_out_ctx.m_vblank_status = SceVideoOutVblankStatus();
|
||||
|
||||
return handle;
|
||||
}
|
||||
void VideoOutCtx::Close(s32 handle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
m_video_out_ctx.isOpened = false;
|
||||
|
||||
if (m_video_out_ctx.m_flip_evtEq.size() > 0) {
|
||||
BREAKPOINT(); // we need to clear all events if they have been created
|
||||
}
|
||||
|
||||
m_video_out_ctx.m_flip_rate = 0;
|
||||
|
||||
// clear buffers
|
||||
for (auto& buffer : m_video_out_ctx.buffers) {
|
||||
buffer.buffer = nullptr;
|
||||
buffer.buffer_render = nullptr;
|
||||
buffer.buffer_size = 0;
|
||||
buffer.set_id = 0;
|
||||
}
|
||||
|
||||
m_video_out_ctx.buffers_sets.clear();
|
||||
|
||||
m_video_out_ctx.buffers_registration_index = 0;
|
||||
}
|
||||
|
||||
VideoConfigInternal* VideoOutCtx::getCtx(int handle) {
|
||||
if (handle != 1) [[unlikely]] {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_video_out_ctx; // assuming that it's the only ctx TODO check if we need more
|
||||
}
|
||||
|
||||
void FlipQueue::getFlipStatus(VideoConfigInternal* cfg, SceVideoOutFlipStatus* out) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
*out = cfg->m_flip_status;
|
||||
}
|
||||
|
||||
bool FlipQueue::submitFlip(VideoConfigInternal* cfg, s32 index, s64 flip_arg) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (m_requests.size() >= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Request r{};
|
||||
r.cfg = cfg;
|
||||
r.index = index;
|
||||
r.flip_arg = flip_arg;
|
||||
r.submit_tsc = Core::Libraries::LibKernel::sceKernelReadTsc();
|
||||
|
||||
m_requests.push_back(r);
|
||||
|
||||
cfg->m_flip_status.flipPendingNum = static_cast<int>(m_requests.size());
|
||||
cfg->m_flip_status.gcQueueNum = 0;
|
||||
|
||||
m_submit_cond.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FlipQueue::flip(u32 micros) {
|
||||
const auto request = [&]() -> Request* {
|
||||
std::unique_lock lock{m_mutex};
|
||||
m_submit_cond.wait_for(lock, std::chrono::microseconds(micros), [&] { return !m_requests.empty(); });
|
||||
if (m_requests.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_requests.at(0); // Process first request
|
||||
}();
|
||||
|
||||
if (!request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto buffer = request->cfg->buffers[request->index].buffer_render;
|
||||
Emu::DrawBuffer(buffer);
|
||||
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
{
|
||||
std::scoped_lock cfg_lock{request->cfg->m_mutex};
|
||||
for (auto& flip_eq : request->cfg->m_flip_evtEq) {
|
||||
if (flip_eq != nullptr) {
|
||||
flip_eq->triggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, HLE::Kernel::Objects::EVFILT_VIDEO_OUT, reinterpret_cast<void*>(request->flip_arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_requests.erase(m_requests.begin());
|
||||
m_done_cond.notify_one();
|
||||
|
||||
request->cfg->m_flip_status.count++;
|
||||
request->cfg->m_flip_status.processTime = Core::Libraries::LibKernel::sceKernelGetProcessTime();
|
||||
request->cfg->m_flip_status.tsc = Core::Libraries::LibKernel::sceKernelReadTsc();
|
||||
request->cfg->m_flip_status.submitTsc = request->submit_tsc;
|
||||
request->cfg->m_flip_status.flipArg = request->flip_arg;
|
||||
request->cfg->m_flip_status.currentBuffer = request->index;
|
||||
request->cfg->m_flip_status.flipPendingNum = static_cast<int>(m_requests.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace HLE::Graphics::Objects
|
81
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h
Normal file
81
src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <core/PS4/HLE/Graphics/video_out.h>
|
||||
#include <core/PS4/HLE/Graphics/graphics_ctx.h>
|
||||
#include <emulator.h>
|
||||
|
||||
using namespace HLE::Libs::Graphics::VideoOut;
|
||||
|
||||
namespace HLE::Graphics::Objects {
|
||||
|
||||
struct VideoOutBufferInfo {
|
||||
const void* buffer = nullptr;
|
||||
HLE::Libs::Graphics::VideoOutVulkanImage* buffer_render = nullptr;
|
||||
u64 buffer_size = 0;
|
||||
u64 buffer_pitch = 0;
|
||||
int set_id = 0;
|
||||
};
|
||||
|
||||
struct VideoConfigInternal {
|
||||
std::mutex m_mutex;
|
||||
SceVideoOutResolutionStatus m_resolution;
|
||||
bool isOpened = false;
|
||||
SceVideoOutFlipStatus m_flip_status;
|
||||
SceVideoOutVblankStatus m_vblank_status;
|
||||
std::vector<HLE::Libs::LibKernel::EventQueues::SceKernelEqueue> m_flip_evtEq;
|
||||
int m_flip_rate = 0;
|
||||
VideoOutBufferInfo buffers[16];
|
||||
std::vector<VideoOutBufferSetInternal> buffers_sets;
|
||||
int buffers_registration_index = 0;
|
||||
};
|
||||
|
||||
class FlipQueue {
|
||||
public:
|
||||
FlipQueue() {}
|
||||
virtual ~FlipQueue() {}
|
||||
|
||||
void getFlipStatus(VideoConfigInternal* cfg, SceVideoOutFlipStatus* out);
|
||||
bool submitFlip(VideoConfigInternal* cfg, s32 index, s64 flip_arg);
|
||||
bool flip(u32 micros);
|
||||
private:
|
||||
struct Request {
|
||||
VideoConfigInternal* cfg;
|
||||
int index;
|
||||
int64_t flip_arg;
|
||||
uint64_t submit_tsc;
|
||||
};
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_submit_cond;
|
||||
std::condition_variable m_done_cond;
|
||||
std::vector<Request> m_requests;
|
||||
};
|
||||
|
||||
class VideoOutCtx {
|
||||
|
||||
public:
|
||||
VideoOutCtx() {}
|
||||
virtual ~VideoOutCtx() {}
|
||||
void Init(u32 width, u32 height);
|
||||
int Open();
|
||||
void Close(s32 handle);
|
||||
VideoConfigInternal* getCtx(int handle);
|
||||
FlipQueue& getFlipQueue() { return m_flip_queue; }
|
||||
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (!m_graphic_ctx) {
|
||||
m_graphic_ctx = Emu::getGraphicCtx();
|
||||
}
|
||||
|
||||
return m_graphic_ctx;
|
||||
}
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
VideoConfigInternal m_video_out_ctx;
|
||||
FlipQueue m_flip_queue;
|
||||
HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr;
|
||||
};
|
||||
}; // namespace HLE::Graphics::Objects
|
73
src/core/PS4/HLE/Graphics/graphics_ctx.h
Normal file
73
src/core/PS4/HLE/Graphics/graphics_ctx.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <types.h>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace HLE::Libs::Graphics {
|
||||
|
||||
struct VulkanCommandPool {
|
||||
std::mutex mutex;
|
||||
VkCommandPool pool = nullptr;
|
||||
std::vector<VkCommandBuffer> buffers;
|
||||
std::vector<VkFence> fences;
|
||||
std::vector<VkSemaphore> semaphores;
|
||||
std::vector<bool> busy;
|
||||
u32 buffers_count = 0;
|
||||
};
|
||||
|
||||
struct VulkanQueueInfo {
|
||||
std::unique_ptr<std::mutex> mutex{};
|
||||
u32 family = static_cast<u32>(-1);
|
||||
u32 index = static_cast<u32>(-1);
|
||||
VkQueue vk_queue = nullptr;
|
||||
};
|
||||
|
||||
struct GraphicCtx {
|
||||
u32 screen_width = 0;
|
||||
u32 screen_height = 0;
|
||||
VkInstance m_instance = nullptr;
|
||||
VkPhysicalDevice m_physical_device = nullptr;
|
||||
VkDevice m_device = nullptr;
|
||||
VulkanQueueInfo queues[11]; // VULKAN_QUEUES_NUM
|
||||
};
|
||||
|
||||
enum class VulkanImageType { Unknown, VideoOut };
|
||||
|
||||
struct VulkanMemory {
|
||||
VkMemoryRequirements requirements = {};
|
||||
VkMemoryPropertyFlags property = 0;
|
||||
VkDeviceMemory memory = nullptr;
|
||||
VkDeviceSize offset = 0;
|
||||
u32 type = 0;
|
||||
u64 unique_id = 0;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
VkBuffer buffer = nullptr;
|
||||
VulkanMemory memory;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
};
|
||||
struct VulkanImage {
|
||||
static constexpr int VIEW_MAX = 4;
|
||||
static constexpr int VIEW_DEFAULT = 0;
|
||||
static constexpr int VIEW_BGRA = 1;
|
||||
static constexpr int VIEW_DEPTH_TEXTURE = 2;
|
||||
|
||||
explicit VulkanImage(VulkanImageType type) : type(type) {}
|
||||
|
||||
VulkanImageType type = VulkanImageType::Unknown;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {};
|
||||
VkImage image = nullptr;
|
||||
VkImageView image_view[VIEW_MAX] = {};
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VulkanMemory memory;
|
||||
};
|
||||
|
||||
struct VideoOutVulkanImage : public VulkanImage {
|
||||
VideoOutVulkanImage() : VulkanImage(VulkanImageType::VideoOut) {}
|
||||
};
|
||||
|
||||
} // namespace HLE::Libs::Graphics
|
201
src/core/PS4/HLE/Graphics/graphics_render.cpp
Normal file
201
src/core/PS4/HLE/Graphics/graphics_render.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
#include "graphics_render.h"
|
||||
#include <fmt/core.h>
|
||||
#include "Emulator/Util/singleton.h"
|
||||
#include "emulator.h"
|
||||
|
||||
static thread_local GPU::CommandPool g_command_pool;
|
||||
|
||||
void GPU::renderCreateCtx() {
|
||||
auto* render_ctx = singleton<RenderCtx>::instance();
|
||||
|
||||
render_ctx->setGraphicCtx(Emu::getGraphicCtx());
|
||||
}
|
||||
|
||||
void GPU::CommandBuffer::allocateBuffer() {
|
||||
m_pool = g_command_pool.getPool(m_queue);
|
||||
|
||||
std::scoped_lock lock{m_pool->mutex};
|
||||
|
||||
for (uint32_t i = 0; i < m_pool->buffers_count; i++) {
|
||||
if (!m_pool->busy[i]) {
|
||||
m_pool->busy[i] = true;
|
||||
vkResetCommandBuffer(m_pool->buffers[i], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
|
||||
m_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GPU::CommandBuffer::freeBuffer() {
|
||||
std::scoped_lock lock{m_pool->mutex};
|
||||
|
||||
waitForFence();
|
||||
|
||||
m_pool->busy[m_index] = false;
|
||||
vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
|
||||
m_index = static_cast<uint32_t>(-1);
|
||||
}
|
||||
|
||||
void GPU::CommandBuffer::waitForFence() {
|
||||
auto* render_ctx = singleton<RenderCtx>::instance();
|
||||
|
||||
if (m_execute) {
|
||||
auto* device = render_ctx->getGraphicCtx()->m_device;
|
||||
|
||||
vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
|
||||
vkResetFences(device, 1, &m_pool->fences[m_index]);
|
||||
|
||||
m_execute = false;
|
||||
}
|
||||
}
|
||||
void GPU::CommandBuffer::begin() const {
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
|
||||
VkCommandBufferBeginInfo begin_info{};
|
||||
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin_info.pNext = nullptr;
|
||||
begin_info.flags = 0;
|
||||
begin_info.pInheritanceInfo = nullptr;
|
||||
|
||||
auto result = vkBeginCommandBuffer(buffer, &begin_info);
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
fmt::print("vkBeginCommandBuffer failed\n");
|
||||
std::exit(0);
|
||||
}
|
||||
}
|
||||
void GPU::CommandBuffer::end() const {
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
|
||||
auto result = vkEndCommandBuffer(buffer);
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
fmt::print("vkEndCommandBuffer failed\n");
|
||||
std::exit(0);
|
||||
}
|
||||
}
|
||||
void GPU::CommandBuffer::executeWithSemaphore() {
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
auto* fence = m_pool->fences[m_index];
|
||||
|
||||
VkSubmitInfo submit_info{};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = 0;
|
||||
submit_info.pWaitSemaphores = nullptr;
|
||||
submit_info.pWaitDstStageMask = nullptr;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &m_pool->semaphores[m_index];
|
||||
|
||||
auto* render_ctx = singleton<RenderCtx>::instance();
|
||||
const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue];
|
||||
auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
|
||||
|
||||
m_execute = true;
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
fmt::print("vkQueueSubmit failed\n");
|
||||
std::exit(0);
|
||||
}
|
||||
}
|
||||
void GPU::CommandBuffer::execute() {
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
auto* fence = m_pool->fences[m_index];
|
||||
|
||||
VkSubmitInfo submit_info{};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = 0;
|
||||
submit_info.pWaitSemaphores = nullptr;
|
||||
submit_info.pWaitDstStageMask = nullptr;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = 0;
|
||||
submit_info.pSignalSemaphores = nullptr;
|
||||
|
||||
auto* render_ctx = singleton<RenderCtx>::instance();
|
||||
const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue];
|
||||
auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
|
||||
|
||||
m_execute = true;
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
fmt::print("vkQueueSubmit failed\n");
|
||||
std::exit(0);
|
||||
}
|
||||
}
|
||||
void GPU::CommandPool::createPool(int id) {
|
||||
auto* render_ctx = singleton<RenderCtx>::instance();
|
||||
auto* ctx = render_ctx->getGraphicCtx();
|
||||
|
||||
VkCommandPoolCreateInfo pool_info{};
|
||||
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
pool_info.pNext = nullptr;
|
||||
pool_info.queueFamilyIndex = ctx->queues[id].family;
|
||||
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
|
||||
vkCreateCommandPool(ctx->m_device, &pool_info, nullptr, &m_pool[id].pool);
|
||||
|
||||
if (!m_pool[id].pool) {
|
||||
fmt::print("pool is nullptr");
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
m_pool[id].buffers_count = 4;
|
||||
m_pool[id].buffers.resize(m_pool[id].buffers_count);
|
||||
m_pool[id].fences.resize(m_pool[id].buffers_count);
|
||||
m_pool[id].semaphores.resize(m_pool[id].buffers_count);
|
||||
m_pool[id].busy.resize(m_pool[id].buffers_count);
|
||||
|
||||
VkCommandBufferAllocateInfo alloc_info{};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
alloc_info.commandPool = m_pool[id].pool;
|
||||
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
alloc_info.commandBufferCount = m_pool[id].buffers_count;
|
||||
|
||||
if (vkAllocateCommandBuffers(ctx->m_device, &alloc_info, m_pool[id].buffers.data()) != VK_SUCCESS) {
|
||||
fmt::print("Can't allocate command buffers\n");
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < m_pool[id].buffers_count; i++) {
|
||||
m_pool[id].busy[i] = false;
|
||||
|
||||
VkFenceCreateInfo fence_info{};
|
||||
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_info.pNext = nullptr;
|
||||
fence_info.flags = 0;
|
||||
|
||||
if (vkCreateFence(ctx->m_device, &fence_info, nullptr, &m_pool[id].fences[i]) != VK_SUCCESS) {
|
||||
fmt::print("Can't create fence\n");
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo semaphore_info{};
|
||||
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
semaphore_info.pNext = nullptr;
|
||||
semaphore_info.flags = 0;
|
||||
|
||||
if (vkCreateSemaphore(ctx->m_device, &semaphore_info, nullptr, &m_pool[id].semaphores[i]) != VK_SUCCESS) {
|
||||
fmt::print("Can't create semas\n");
|
||||
std::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GPU::CommandPool::deleteAllPool() {
|
||||
auto* render_ctx = singleton<RenderCtx>::instance();
|
||||
auto* ctx = render_ctx->getGraphicCtx();
|
||||
|
||||
for (auto& pool : m_pool) {
|
||||
if (pool.pool) {
|
||||
for (u32 i = 0; i < pool.buffers_count; i++) {
|
||||
vkDestroySemaphore(ctx->m_device, pool.semaphores[i], nullptr);
|
||||
vkDestroyFence(ctx->m_device, pool.fences[i], nullptr);
|
||||
}
|
||||
vkDestroyCommandPool(ctx->m_device, pool.pool, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
65
src/core/PS4/HLE/Graphics/graphics_render.h
Normal file
65
src/core/PS4/HLE/Graphics/graphics_render.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "graphics_ctx.h"
|
||||
|
||||
namespace GPU {
|
||||
|
||||
class CommandPool {
|
||||
public:
|
||||
CommandPool() = default;
|
||||
~CommandPool() {}
|
||||
|
||||
HLE::Libs::Graphics::VulkanCommandPool* getPool(int id) {
|
||||
if (!m_pool[id].pool) {
|
||||
createPool(id);
|
||||
}
|
||||
return &m_pool[id];
|
||||
}
|
||||
|
||||
private:
|
||||
void createPool(int id);
|
||||
void deleteAllPool();
|
||||
|
||||
std::array<HLE::Libs::Graphics::VulkanCommandPool, 11> m_pool{};
|
||||
};
|
||||
class CommandBuffer {
|
||||
public:
|
||||
explicit CommandBuffer(int queue) : m_queue(queue) { allocateBuffer(); }
|
||||
virtual ~CommandBuffer() { freeBuffer(); }
|
||||
void allocateBuffer();
|
||||
void freeBuffer();
|
||||
void waitForFence();
|
||||
void begin() const;
|
||||
void end() const;
|
||||
void executeWithSemaphore();
|
||||
void execute();
|
||||
u32 getIndex() const { return m_index; }
|
||||
HLE::Libs::Graphics::VulkanCommandPool* getPool() { return m_pool; }
|
||||
private:
|
||||
int m_queue = -1;
|
||||
u32 m_index = static_cast<u32>(-1);
|
||||
HLE::Libs::Graphics::VulkanCommandPool* m_pool = nullptr;
|
||||
bool m_execute = false;
|
||||
};
|
||||
|
||||
class Framebuffer {
|
||||
public:
|
||||
Framebuffer() {}
|
||||
virtual ~Framebuffer() {}
|
||||
};
|
||||
class RenderCtx {
|
||||
public:
|
||||
RenderCtx() = default;
|
||||
|
||||
virtual ~RenderCtx() {}
|
||||
void setGraphicCtx(HLE::Libs::Graphics::GraphicCtx* ctx) { m_graphic_ctx = ctx; }
|
||||
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() { return m_graphic_ctx; }
|
||||
|
||||
private:
|
||||
Framebuffer m_framebuffer{};
|
||||
HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr;
|
||||
};
|
||||
|
||||
void renderCreateCtx();
|
||||
}; // namespace GPU
|
322
src/core/PS4/HLE/Graphics/video_out.cpp
Normal file
322
src/core/PS4/HLE/Graphics/video_out.cpp
Normal file
|
@ -0,0 +1,322 @@
|
|||
#include "video_out.h"
|
||||
|
||||
#include <core/PS4/GPU/gpu_memory.h>
|
||||
#include <core/PS4/GPU/video_out_buffer.h>
|
||||
#include <core/PS4/HLE/ErrorCodes.h>
|
||||
#include <core/PS4/HLE/LibSceGnmDriver.h>
|
||||
#include <core/PS4/HLE/Libs.h>
|
||||
#include <core/PS4/HLE/UserManagement/UsrMngCodes.h>
|
||||
#include <Util/config.h>
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "Objects/video_out_ctx.h"
|
||||
#include "Emulator/Util/singleton.h"
|
||||
#include "emulator.h"
|
||||
#include "graphics_render.h"
|
||||
|
||||
namespace HLE::Libs::Graphics::VideoOut {
|
||||
|
||||
constexpr bool log_file_videoout = true; // disable it to disable logging
|
||||
|
||||
void videoOutInit(u32 width, u32 height) {
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
videoOut->Init(width, height);
|
||||
}
|
||||
|
||||
bool videoOutFlip(u32 micros) {
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
return videoOut->getFlipQueue().flip(micros);
|
||||
}
|
||||
|
||||
std::string getPixelFormatString(s32 format) {
|
||||
switch (format) {
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB: return "PIXEL_FORMAT_A8R8G8B8_SRGB";
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB: return "PIXEL_FORMAT_A8B8G8R8_SRGB";
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10: return "PIXEL_FORMAT_A2R10G10B10";
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB: return "PIXEL_FORMAT_A2R10G10B10_SRGB";
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ: return "PIXEL_FORMAT_A2R10G10B10_BT2020_PQ";
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT: return "PIXEL_FORMAT_A16R16G16B16_FLOAT";
|
||||
case SCE_VIDEO_OUT_PIXEL_FORMAT_YCBCR420_BT709: return "PIXEL_FORMAT_YCBCR420_BT709";
|
||||
default: return "Unknown pixel format";
|
||||
}
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute, u32 pixelFormat, u32 tilingMode, u32 aspectRatio, u32 width,
|
||||
u32 height, u32 pitchInPixel) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
|
||||
auto tileMode = magic_enum::enum_cast<SceVideoOutTilingMode>(tilingMode);
|
||||
auto aspectR = magic_enum::enum_cast<AspectRatioMode>(aspectRatio);
|
||||
|
||||
LOG_INFO_IF(log_file_videoout, "pixelFormat = {}\n", getPixelFormatString(pixelFormat));
|
||||
LOG_INFO_IF(log_file_videoout, "tilingMode = {}\n", magic_enum::enum_name(tileMode.value()));
|
||||
LOG_INFO_IF(log_file_videoout, "aspectRatio = {}\n", magic_enum::enum_name(aspectR.value()));
|
||||
LOG_INFO_IF(log_file_videoout, "width = {}\n", width);
|
||||
LOG_INFO_IF(log_file_videoout, "height = {}\n", height);
|
||||
LOG_INFO_IF(log_file_videoout, "pitchInPixel = {}\n", pitchInPixel);
|
||||
|
||||
memset(attribute, 0, sizeof(SceVideoOutBufferAttribute));
|
||||
|
||||
attribute->pixelFormat = pixelFormat;
|
||||
attribute->tilingMode = tilingMode;
|
||||
attribute->aspectRatio = aspectRatio;
|
||||
attribute->width = width;
|
||||
attribute->height = height;
|
||||
attribute->pitchInPixel = pitchInPixel;
|
||||
attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE;
|
||||
}
|
||||
|
||||
static void flip_reset_event_func(HLE::Kernel::Objects::EqueueEvent* event) {
|
||||
event->isTriggered = false;
|
||||
event->event.fflags = 0;
|
||||
event->event.data = 0;
|
||||
}
|
||||
static void flip_trigger_event_func(HLE::Kernel::Objects::EqueueEvent* event, void* trigger_data) {
|
||||
event->isTriggered = true;
|
||||
event->event.fflags++;
|
||||
event->event.data = reinterpret_cast<intptr_t>(trigger_data);
|
||||
}
|
||||
static void flip_delete_event_func(LibKernel::EventQueues::SceKernelEqueue eq, HLE::Kernel::Objects::EqueueEvent* event) {
|
||||
BREAKPOINT(); // TODO
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(LibKernel::EventQueues::SceKernelEqueue eq, s32 handle, void* udata) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
|
||||
auto* ctx = videoOut->getCtx(handle);
|
||||
|
||||
if (ctx == nullptr) {
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
std::scoped_lock lock(ctx->m_mutex);
|
||||
|
||||
if (eq == nullptr) {
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE;
|
||||
}
|
||||
|
||||
HLE::Kernel::Objects::EqueueEvent event;
|
||||
event.isTriggered = false;
|
||||
event.event.ident = SCE_VIDEO_OUT_EVENT_FLIP;
|
||||
event.event.filter = HLE::Kernel::Objects::EVFILT_VIDEO_OUT;
|
||||
event.event.udata = udata;
|
||||
event.event.fflags = 0;
|
||||
event.event.data = 0;
|
||||
event.filter.delete_event_func = flip_delete_event_func;
|
||||
event.filter.reset_event_func = flip_reset_event_func;
|
||||
event.filter.trigger_event_func = flip_trigger_event_func;
|
||||
event.filter.data = ctx;
|
||||
|
||||
int result = eq->addEvent(event);
|
||||
|
||||
ctx->m_flip_evtEq.push_back(eq);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum,
|
||||
const SceVideoOutBufferAttribute* attribute) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
auto* ctx = videoOut->getCtx(handle);
|
||||
|
||||
if (handle == 1) { // main port
|
||||
if (startIndex < 0 || startIndex > 15) {
|
||||
LOG_TRACE_IF(log_file_videoout, "invalid startIndex = {}\n", startIndex);
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
|
||||
}
|
||||
if (bufferNum < 1 || bufferNum > 16) {
|
||||
LOG_TRACE_IF(log_file_videoout, "invalid bufferNum = {}\n", bufferNum);
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
|
||||
}
|
||||
}
|
||||
if (addresses == nullptr) {
|
||||
LOG_TRACE_IF(log_file_videoout, "addresses are null\n");
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (attribute == nullptr) {
|
||||
LOG_TRACE_IF(log_file_videoout, "attribute is null\n");
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_OPTION;
|
||||
}
|
||||
if (attribute->aspectRatio != 0) {
|
||||
LOG_TRACE_IF(log_file_videoout, "invalid aspect ratio = {}\n", attribute->aspectRatio);
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO;
|
||||
}
|
||||
if (attribute->tilingMode < 0 || attribute->tilingMode > 1) {
|
||||
LOG_TRACE_IF(log_file_videoout, "invalid tilingMode = {}\n", attribute->tilingMode);
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE;
|
||||
}
|
||||
LOG_INFO_IF(log_file_videoout, "startIndex = {}\n", startIndex);
|
||||
LOG_INFO_IF(log_file_videoout, "bufferNum = {}\n", bufferNum);
|
||||
LOG_INFO_IF(log_file_videoout, "pixelFormat = {}\n", log_hex_full(attribute->pixelFormat));
|
||||
LOG_INFO_IF(log_file_videoout, "tilingMode = {}\n", attribute->tilingMode);
|
||||
LOG_INFO_IF(log_file_videoout, "aspectRatio = {}\n", attribute->aspectRatio);
|
||||
LOG_INFO_IF(log_file_videoout, "width = {}\n", attribute->width);
|
||||
LOG_INFO_IF(log_file_videoout, "height = {}\n", attribute->height);
|
||||
LOG_INFO_IF(log_file_videoout, "pitchInPixel = {}\n", attribute->pitchInPixel);
|
||||
LOG_INFO_IF(log_file_videoout, "option = {}\n", attribute->option);
|
||||
|
||||
int registration_index = ctx->buffers_registration_index++;
|
||||
|
||||
Emu::checkAndWaitForGraphicsInit();
|
||||
GPU::renderCreateCtx();
|
||||
|
||||
// try to calculate buffer size
|
||||
u64 buffer_size = 0; // still calculation is probably partial or wrong :D
|
||||
if (attribute->tilingMode == 0) {
|
||||
buffer_size = 1920 * 1088 * 4;
|
||||
} else {
|
||||
buffer_size = 1920 * 1080 * 4;
|
||||
}
|
||||
u64 buffer_pitch = attribute->pitchInPixel;
|
||||
|
||||
VideoOutBufferSetInternal buf{};
|
||||
|
||||
buf.start_index = startIndex;
|
||||
buf.num = bufferNum;
|
||||
buf.set_id = registration_index;
|
||||
buf.attr = *attribute;
|
||||
|
||||
ctx->buffers_sets.push_back(buf);
|
||||
|
||||
GPU::VideoOutBufferFormat format = GPU::VideoOutBufferFormat::Unknown;
|
||||
|
||||
if (attribute->pixelFormat == 0x80000000) {
|
||||
format = GPU::VideoOutBufferFormat::B8G8R8A8Srgb;
|
||||
} else if (attribute->pixelFormat == 0x80002200) {
|
||||
format = GPU::VideoOutBufferFormat::R8G8B8A8Srgb;
|
||||
}
|
||||
|
||||
GPU::VideoOutBufferObj buffer_info(format, attribute->width, attribute->height, attribute->tilingMode == 0, Config::isNeoMode(), buffer_pitch);
|
||||
|
||||
for (int i = 0; i < bufferNum; i++) {
|
||||
if (ctx->buffers[i + startIndex].buffer != nullptr) {
|
||||
LOG_TRACE_IF(log_file_videoout, "buffer slot {} is occupied!\n", i + startIndex);
|
||||
return SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED;
|
||||
}
|
||||
|
||||
ctx->buffers[i + startIndex].set_id = registration_index;
|
||||
ctx->buffers[i + startIndex].buffer = addresses[i];
|
||||
ctx->buffers[i + startIndex].buffer_size = buffer_size;
|
||||
ctx->buffers[i + startIndex].buffer_pitch = buffer_pitch;
|
||||
ctx->buffers[i + startIndex].buffer_render = static_cast<Graphics::VideoOutVulkanImage*>(
|
||||
GPU::memoryCreateObj(0, videoOut->getGraphicCtx(), nullptr, reinterpret_cast<uint64_t>(addresses[i]), buffer_size, buffer_info));
|
||||
|
||||
LOG_INFO_IF(log_file_videoout, "buffers[{}] = {}\n", i + startIndex, log_hex_full(reinterpret_cast<uint64_t>(addresses[i])));
|
||||
}
|
||||
|
||||
return registration_index;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
videoOut->getCtx(handle)->m_flip_rate = rate;
|
||||
return SCE_OK;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
s32 pending = videoOut->getCtx(handle)->m_flip_status.flipPendingNum;
|
||||
return pending;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
auto* ctx = videoOut->getCtx(handle);
|
||||
|
||||
if (flipMode != 1) {
|
||||
// BREAKPOINT(); // only flipmode==1 is supported
|
||||
LOG_TRACE_IF(log_file_videoout, "sceVideoOutSubmitFlip flipmode {}\n", bufferIndex);//openBOR needs 2 but seems to work
|
||||
}
|
||||
if (bufferIndex == -1) {
|
||||
BREAKPOINT(); // blank output not supported
|
||||
}
|
||||
if (bufferIndex < -1 || bufferIndex > 15) {
|
||||
LOG_TRACE_IF(log_file_videoout, "sceVideoOutSubmitFlip invalid bufferIndex {}\n", bufferIndex);
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_INDEX;
|
||||
}
|
||||
LOG_INFO_IF(log_file_videoout, "bufferIndex = {}\n", bufferIndex);
|
||||
LOG_INFO_IF(log_file_videoout, "flipMode = {}\n", flipMode);
|
||||
LOG_INFO_IF(log_file_videoout, "flipArg = {}\n", flipArg);
|
||||
|
||||
if (!videoOut->getFlipQueue().submitFlip(ctx, bufferIndex, flipArg)) {
|
||||
LOG_TRACE_IF(log_file_videoout, "sceVideoOutSubmitFlip flip queue is full\n");
|
||||
return SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
|
||||
}
|
||||
HLE::Libs::LibSceGnmDriver::sceGnmFlushGarlic(); // hackish should be done that neccesary for niko's homebrew
|
||||
return SCE_OK;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
auto* ctx = videoOut->getCtx(handle);
|
||||
videoOut->getFlipQueue().getFlipStatus(ctx, status);
|
||||
|
||||
LOG_INFO_IF(log_file_videoout, "count = {}\n", status->count);
|
||||
LOG_INFO_IF(log_file_videoout, "processTime = {}\n", status->processTime);
|
||||
LOG_INFO_IF(log_file_videoout, "tsc = {}\n", status->tsc);
|
||||
LOG_INFO_IF(log_file_videoout, "submitTsc = {}\n", status->submitTsc);
|
||||
LOG_INFO_IF(log_file_videoout, "flipArg = {}\n", status->flipArg);
|
||||
LOG_INFO_IF(log_file_videoout, "gcQueueNum = {}\n", status->gcQueueNum);
|
||||
LOG_INFO_IF(log_file_videoout, "flipPendingNum = {}\n", status->flipPendingNum);
|
||||
LOG_INFO_IF(log_file_videoout, "currentBuffer = {}\n", status->currentBuffer);
|
||||
return 0;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
*status = videoOut->getCtx(handle)->m_resolution;
|
||||
return SCE_OK;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, const void* param) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
if (userId != SCE_USER_SERVICE_USER_ID_SYSTEM && userId != 0) {
|
||||
BREAKPOINT();
|
||||
}
|
||||
if (busType != SCE_VIDEO_OUT_BUS_TYPE_MAIN) {
|
||||
BREAKPOINT();
|
||||
}
|
||||
if (index != 0) {
|
||||
LOG_TRACE_IF(log_file_videoout, "sceVideoOutOpen index!=0\n");
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
|
||||
}
|
||||
if (param != nullptr) {
|
||||
BREAKPOINT();
|
||||
}
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
int handle = videoOut->Open();
|
||||
|
||||
if (handle < 0) {
|
||||
LOG_TRACE_IF(log_file_videoout, "sceVideoOutOpen all available handles are open\n");
|
||||
return SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY; // it is alreadyOpened
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle) {
|
||||
auto* videoOut = singleton<HLE::Graphics::Objects::VideoOutCtx>::instance();
|
||||
videoOut->Close(handle);
|
||||
return SCE_OK;
|
||||
}
|
||||
s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) { BREAKPOINT(); }
|
||||
|
||||
void videoOutRegisterLib(SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetFlipStatus);
|
||||
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip);
|
||||
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutRegisterBuffers);
|
||||
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutAddFlipEvent);
|
||||
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSetFlipRate);
|
||||
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSetBufferAttribute);
|
||||
LIB_FUNCTION("6kPnj51T62Y", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetResolutionStatus);
|
||||
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutOpen);
|
||||
LIB_FUNCTION("zgXifHT9ErY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutIsFlipPending);
|
||||
LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutUnregisterBuffers);
|
||||
LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose);
|
||||
}
|
||||
} // namespace HLE::Libs::Graphics::VideoOut
|
114
src/core/PS4/HLE/Graphics/video_out.h
Normal file
114
src/core/PS4/HLE/Graphics/video_out.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
#include <core/PS4/HLE/Kernel/event_queues.h>
|
||||
#include <core/PS4/Loader/SymbolsResolver.h>
|
||||
#include <types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace HLE::Libs::Graphics::VideoOut {
|
||||
|
||||
using SceUserServiceUserId = s32; // TODO move it to proper place
|
||||
|
||||
// SceVideoOutBusType
|
||||
constexpr int SCE_VIDEO_OUT_BUS_TYPE_MAIN = 0; // Main output
|
||||
constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_SOCIAL_SCREEN = 5; // Aux output for social
|
||||
constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_GAME_LIVE_STREAMING = 6; // Aux output for screaming
|
||||
|
||||
// SceVideoOutRefreshRate
|
||||
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_UNKNOWN = 0;
|
||||
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_23_98HZ = 1;
|
||||
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_50HZ = 2;
|
||||
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_59_94HZ = 3;
|
||||
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_119_88HZ = 13;
|
||||
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_89_91HZ = 35;
|
||||
constexpr s64 SCE_VIDEO_OUT_REFRESH_RATE_ANY = 0xFFFFFFFFFFFFFFFFUL;
|
||||
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB = 0x80000000;
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB = 0x80002200;
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10 = 0x88060000;
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB = 0x88000000;
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ = 0x88740000;
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT = 0xC1060000;
|
||||
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_YCBCR420_BT709 = 0x08322200;
|
||||
|
||||
constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0;
|
||||
constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_VR = 7;
|
||||
constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8;
|
||||
|
||||
enum SceVideoOutEventId : s16 { SCE_VIDEO_OUT_EVENT_FLIP = 0, SCE_VIDEO_OUT_EVENT_VBLANK = 1, SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2 };
|
||||
|
||||
enum SceVideoOutTilingMode : s32 { SCE_VIDEO_OUT_TILING_MODE_TILE = 0, SCE_VIDEO_OUT_TILING_MODE_LINEAR = 1 };
|
||||
|
||||
enum AspectRatioMode : s32 { SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0 };
|
||||
|
||||
struct SceVideoOutBufferAttribute {
|
||||
s32 pixelFormat;
|
||||
s32 tilingMode;
|
||||
s32 aspectRatio;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 pitchInPixel;
|
||||
u32 option;
|
||||
u32 reserved0;
|
||||
u64 reserved1;
|
||||
};
|
||||
|
||||
struct SceVideoOutFlipStatus {
|
||||
u64 count = 0;
|
||||
u64 processTime = 0;
|
||||
u64 tsc = 0;
|
||||
s64 flipArg = 0;
|
||||
u64 submitTsc = 0;
|
||||
u64 reserved0 = 0;
|
||||
s32 gcQueueNum = 0;
|
||||
s32 flipPendingNum = 0;
|
||||
s32 currentBuffer = 0;
|
||||
u32 reserved1 = 0;
|
||||
};
|
||||
|
||||
struct SceVideoOutResolutionStatus {
|
||||
s32 fullWidth = 1280;
|
||||
s32 fullHeight = 720;
|
||||
s32 paneWidth = 1280;
|
||||
s32 paneHeight = 720;
|
||||
u64 refreshRate = SCE_VIDEO_OUT_REFRESH_RATE_59_94HZ;
|
||||
float screenSizeInInch = 50;
|
||||
u16 flags = 0;
|
||||
u16 reserved0 = 0;
|
||||
u32 reserved1[3] = {0};
|
||||
};
|
||||
|
||||
struct SceVideoOutVblankStatus {
|
||||
u64 count = 0;
|
||||
u64 processTime = 0;
|
||||
u64 tsc = 0;
|
||||
u64 reserved[1] = {0};
|
||||
u08 flags = 0;
|
||||
u08 pad1[7] = {};
|
||||
};
|
||||
|
||||
struct VideoOutBufferSetInternal {
|
||||
SceVideoOutBufferAttribute attr;
|
||||
int start_index = 0;
|
||||
int num = 0;
|
||||
int set_id = 0;
|
||||
};
|
||||
|
||||
void videoOutInit(u32 width, u32 height);
|
||||
std::string getPixelFormatString(s32 format);
|
||||
void videoOutRegisterLib(SymbolsResolver* sym);
|
||||
bool videoOutFlip(u32 micros);
|
||||
|
||||
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute, u32 pixelFormat, u32 tilingMode, u32 aspectRatio, u32 width,
|
||||
u32 height, u32 pitchInPixel);
|
||||
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(LibKernel::EventQueues::SceKernelEqueue eq, s32 handle, void* udata);
|
||||
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum,
|
||||
const SceVideoOutBufferAttribute* attribute);
|
||||
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate);
|
||||
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
|
||||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, const void* param);
|
||||
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
|
||||
} // namespace HLE::Libs::Graphics::VideoOut
|
90
src/core/PS4/HLE/Kernel/Objects/event_queue.cpp
Normal file
90
src/core/PS4/HLE/Kernel/Objects/event_queue.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include "event_queue.h"
|
||||
|
||||
#include <Lib/Timer.h>
|
||||
|
||||
#include "debug.h"
|
||||
|
||||
namespace HLE::Kernel::Objects {
|
||||
EqueueInternal::~EqueueInternal() {}
|
||||
|
||||
int EqueueInternal::addEvent(const EqueueEvent& event) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (m_events.size() > 0) {
|
||||
BREAKPOINT();
|
||||
}
|
||||
// TODO check if event is already exists and return it. Currently we just add in m_events array
|
||||
m_events.push_back(event);
|
||||
|
||||
if (event.isTriggered) {
|
||||
BREAKPOINT(); // we don't support that either yet
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int EqueueInternal::waitForEvents(SceKernelEvent* ev, int num, u32 micros) {
|
||||
std::unique_lock lock{m_mutex};
|
||||
|
||||
u32 timeElapsed = 0;
|
||||
Lib::Timer t;
|
||||
t.Start();
|
||||
|
||||
for (;;) {
|
||||
int ret = getTriggeredEvents(ev, num);
|
||||
|
||||
if (ret > 0 || (timeElapsed >= micros && micros != 0)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (micros == 0) {
|
||||
m_cond.wait(lock);
|
||||
} else {
|
||||
m_cond.wait_for(lock, std::chrono::microseconds(micros - timeElapsed));
|
||||
}
|
||||
|
||||
timeElapsed = static_cast<uint32_t>(t.GetTimeSec() * 1000000.0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
if (m_events.size() > 1) {
|
||||
BREAKPOINT(); // we currently support one event
|
||||
}
|
||||
auto& event = m_events[0];
|
||||
|
||||
if (event.filter.trigger_event_func != nullptr) {
|
||||
event.filter.trigger_event_func(&event, trigger_data);
|
||||
} else {
|
||||
event.isTriggered = true;
|
||||
}
|
||||
|
||||
m_cond.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int EqueueInternal::getTriggeredEvents(SceKernelEvent* ev, int num) {
|
||||
int ret = 0;
|
||||
|
||||
if (m_events.size() > 1) {
|
||||
BREAKPOINT(); // we currently support one event
|
||||
}
|
||||
auto& event = m_events[0];
|
||||
|
||||
if (event.isTriggered) {
|
||||
ev[ret++] = event.event;
|
||||
|
||||
if (event.filter.reset_event_func != nullptr) {
|
||||
event.filter.reset_event_func(&event);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}; // namespace HLE::Kernel::Objects
|
79
src/core/PS4/HLE/Kernel/Objects/event_queue.h
Normal file
79
src/core/PS4/HLE/Kernel/Objects/event_queue.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include <types.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace HLE::Kernel::Objects {
|
||||
|
||||
constexpr s16 EVFILT_READ = -1;
|
||||
constexpr s16 EVFILT_WRITE = -2;
|
||||
constexpr s16 EVFILT_AIO = -3; // attached to aio requests
|
||||
constexpr s16 EVFILT_VNODE = -4; // attached to vnodes
|
||||
constexpr s16 EVFILT_PROC = -5; // attached to struct proc
|
||||
constexpr s16 EVFILT_SIGNAL = -6; // attached to struct proc
|
||||
constexpr s16 EVFILT_TIMER = -7; // timers
|
||||
constexpr s16 EVFILT_FS = -9; // filesystem events
|
||||
constexpr s16 EVFILT_LIO = -10; // attached to lio requests
|
||||
constexpr s16 EVFILT_USER = -11; // User events
|
||||
constexpr s16 EVFILT_POLLING = -12;
|
||||
constexpr s16 EVFILT_VIDEO_OUT = -13;
|
||||
constexpr s16 EVFILT_GRAPHICS_CORE = -14;
|
||||
constexpr s16 EVFILT_HRTIMER = -15;
|
||||
constexpr s16 EVFILT_UVD_TRAP = -16;
|
||||
constexpr s16 EVFILT_VCE_TRAP = -17;
|
||||
constexpr s16 EVFILT_SDMA_TRAP = -18;
|
||||
constexpr s16 EVFILT_REG_EV = -19;
|
||||
constexpr s16 EVFILT_GPU_EXCEPTION = -20;
|
||||
constexpr s16 EVFILT_GPU_SYSTEM_EXCEPTION = -21;
|
||||
constexpr s16 EVFILT_GPU_DBGGC_EV = -22;
|
||||
constexpr s16 EVFILT_SYSCOUNT = 22;
|
||||
|
||||
class EqueueInternal;
|
||||
struct EqueueEvent;
|
||||
|
||||
using SceKernelEqueue = Kernel::Objects::EqueueInternal*;
|
||||
|
||||
using TriggerFunc = void (*)(EqueueEvent* event, void* trigger_data);
|
||||
using ResetFunc = void (*)(EqueueEvent* event);
|
||||
using DeleteFunc = void (*)(SceKernelEqueue eq, EqueueEvent* event);
|
||||
|
||||
struct SceKernelEvent {
|
||||
u64 ident = 0; /* identifier for this event */
|
||||
s16 filter = 0; /* filter for event */
|
||||
u16 flags = 0;
|
||||
u32 fflags = 0;
|
||||
s64 data = 0;
|
||||
void* udata = nullptr; /* opaque user data identifier */
|
||||
};
|
||||
|
||||
struct Filter {
|
||||
void* data = nullptr;
|
||||
TriggerFunc trigger_event_func = nullptr;
|
||||
ResetFunc reset_event_func = nullptr;
|
||||
DeleteFunc delete_event_func = nullptr;
|
||||
};
|
||||
|
||||
struct EqueueEvent {
|
||||
bool isTriggered = false;
|
||||
SceKernelEvent event;
|
||||
Filter filter;
|
||||
};
|
||||
|
||||
class EqueueInternal {
|
||||
public:
|
||||
EqueueInternal() = default;
|
||||
virtual ~EqueueInternal();
|
||||
void setName(const std::string& m_name) { this->m_name = m_name; }
|
||||
int addEvent(const EqueueEvent& event);
|
||||
int waitForEvents(SceKernelEvent* ev, int num, u32 micros);
|
||||
bool triggerEvent(u64 ident, s16 filter, void* trigger_data);
|
||||
int getTriggeredEvents(SceKernelEvent* ev, int num);
|
||||
private:
|
||||
std::string m_name;
|
||||
std::mutex m_mutex;
|
||||
std::vector<EqueueEvent> m_events;
|
||||
std::condition_variable m_cond;
|
||||
};
|
||||
}; // namespace HLE::Kernel::Objects
|
62
src/core/PS4/HLE/Kernel/Objects/physical_memory.cpp
Normal file
62
src/core/PS4/HLE/Kernel/Objects/physical_memory.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include "physical_memory.h"
|
||||
|
||||
namespace HLE::Kernel::Objects {
|
||||
|
||||
static u64 AlignUp(u64 pos, u64 align) { return (align != 0 ? (pos + (align - 1)) & ~(align - 1) : pos); }
|
||||
|
||||
bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut, int memoryType) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
u64 find_free_pos = 0;
|
||||
|
||||
// iterate through allocated blocked and find the next free position
|
||||
for (const auto& block : m_allocatedBlocks) {
|
||||
u64 n = block.start_addr + block.size;
|
||||
if (n > find_free_pos) {
|
||||
find_free_pos = n;
|
||||
}
|
||||
}
|
||||
|
||||
// align free position
|
||||
find_free_pos = AlignUp(find_free_pos, alignment);
|
||||
|
||||
// if the new position is between searchStart - searchEnd , allocate a new block
|
||||
if (find_free_pos >= searchStart && find_free_pos + len <= searchEnd) {
|
||||
AllocatedBlock block{};
|
||||
block.size = len;
|
||||
block.start_addr = find_free_pos;
|
||||
block.memoryType = memoryType;
|
||||
block.gpu_mode = GPU::MemoryMode::NoAccess;
|
||||
block.map_size = 0;
|
||||
block.map_virtual_addr = 0;
|
||||
block.prot = 0;
|
||||
block.cpu_mode = VirtualMemory::MemoryMode::NoAccess;
|
||||
|
||||
m_allocatedBlocks.push_back(block);
|
||||
|
||||
*physAddrOut = find_free_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool PhysicalMemory::Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, VirtualMemory::MemoryMode cpu_mode, GPU::MemoryMode gpu_mode) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
for (auto& b : m_allocatedBlocks) {
|
||||
if (phys_addr >= b.start_addr && phys_addr < b.start_addr + b.size) {
|
||||
if (b.map_virtual_addr != 0 || b.map_size != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
b.map_virtual_addr = virtual_addr;
|
||||
b.map_size = len;
|
||||
b.prot = prot;
|
||||
b.cpu_mode = cpu_mode;
|
||||
b.gpu_mode = gpu_mode;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace HLE::Kernel::Objects
|
34
src/core/PS4/HLE/Kernel/Objects/physical_memory.h
Normal file
34
src/core/PS4/HLE/Kernel/Objects/physical_memory.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include <types.h>
|
||||
#include <core/virtual_memory.h>
|
||||
#include <core/PS4/GPU/gpu_memory.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace HLE::Kernel::Objects {
|
||||
|
||||
class PhysicalMemory {
|
||||
public:
|
||||
struct AllocatedBlock {
|
||||
u64 start_addr;
|
||||
u64 size;
|
||||
int memoryType;
|
||||
u64 map_virtual_addr;
|
||||
u64 map_size;
|
||||
int prot;
|
||||
VirtualMemory::MemoryMode cpu_mode;
|
||||
GPU::MemoryMode gpu_mode;
|
||||
};
|
||||
PhysicalMemory() {}
|
||||
virtual ~PhysicalMemory() {}
|
||||
|
||||
public:
|
||||
bool Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut, int memoryType);
|
||||
bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, VirtualMemory::MemoryMode cpu_mode, GPU::MemoryMode gpu_mode);
|
||||
|
||||
private:
|
||||
std::vector<AllocatedBlock> m_allocatedBlocks;
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace HLE::Kernel::Objects
|
125
src/core/PS4/HLE/Kernel/ThreadManagement.cpp
Normal file
125
src/core/PS4/HLE/Kernel/ThreadManagement.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#include "ThreadManagement.h"
|
||||
|
||||
#include <debug.h>
|
||||
|
||||
#include "../ErrorCodes.h"
|
||||
|
||||
namespace HLE::Libs::LibKernel::ThreadManagement {
|
||||
|
||||
thread_local PthreadInternal* g_pthread_self = nullptr;
|
||||
PThreadCxt* g_pthread_cxt = nullptr;
|
||||
|
||||
void Pthread_Init_Self_MainThread() {
|
||||
g_pthread_self = new PthreadInternal{};
|
||||
scePthreadAttrInit(&g_pthread_self->attr);
|
||||
g_pthread_self->pth = pthread_self();
|
||||
g_pthread_self->name = "Main_Thread";
|
||||
}
|
||||
|
||||
int scePthreadAttrInit(ScePthreadAttr* attr) {
|
||||
*attr = new PthreadAttrInternal{};
|
||||
|
||||
int result = pthread_attr_init(&(*attr)->pth_attr);
|
||||
|
||||
(*attr)->affinity = 0x7f;
|
||||
(*attr)->guard_size = 0x1000;
|
||||
|
||||
SceKernelSchedParam param{};
|
||||
param.sched_priority = 700;
|
||||
|
||||
result = (result == 0 ? scePthreadAttrSetinheritsched(attr, 4) : result);
|
||||
result = (result == 0 ? scePthreadAttrSetschedparam(attr, ¶m) : result);
|
||||
result = (result == 0 ? scePthreadAttrSetschedpolicy(attr, SCHED_OTHER) : result);
|
||||
result = (result == 0 ? scePthreadAttrSetdetachstate(attr, PTHREAD_CREATE_JOINABLE) : result);
|
||||
|
||||
switch (result) {
|
||||
case 0: return SCE_OK;
|
||||
case ENOMEM: return SCE_KERNEL_ERROR_ENOMEM;
|
||||
default: return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate) {
|
||||
if (attr == nullptr || *attr == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int pstate = PTHREAD_CREATE_JOINABLE;
|
||||
switch (detachstate) {
|
||||
case 0: pstate = PTHREAD_CREATE_JOINABLE; break;
|
||||
case 1: pstate = PTHREAD_CREATE_DETACHED; break;
|
||||
default: BREAKPOINT(); // unknown state
|
||||
}
|
||||
|
||||
int result = pthread_attr_setdetachstate(&(*attr)->pth_attr, pstate);
|
||||
|
||||
(*attr)->detached = (pstate == PTHREAD_CREATE_DETACHED);
|
||||
|
||||
if (result == 0) {
|
||||
return SCE_OK;
|
||||
}
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched) {
|
||||
if (attr == nullptr || *attr == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int pinherit_sched = PTHREAD_INHERIT_SCHED;
|
||||
switch (inheritSched) {
|
||||
case 0: pinherit_sched = PTHREAD_EXPLICIT_SCHED; break;
|
||||
case 4: pinherit_sched = PTHREAD_INHERIT_SCHED; break;
|
||||
default: BREAKPOINT(); // unknown inheritSched
|
||||
}
|
||||
|
||||
int result = pthread_attr_setinheritsched(&(*attr)->pth_attr, pinherit_sched);
|
||||
|
||||
if (result == 0) {
|
||||
return SCE_OK;
|
||||
}
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int scePthreadAttrSetschedparam(ScePthreadAttr* attr, const SceKernelSchedParam* param) {
|
||||
if (param == nullptr || attr == nullptr || *attr == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
SceKernelSchedParam pparam{};
|
||||
if (param->sched_priority <= 478) {
|
||||
pparam.sched_priority = +2;
|
||||
} else if (param->sched_priority >= 733) {
|
||||
pparam.sched_priority = -2;
|
||||
} else {
|
||||
pparam.sched_priority = 0;
|
||||
}
|
||||
|
||||
int result = pthread_attr_setschedparam(&(*attr)->pth_attr, &pparam);
|
||||
|
||||
if (result == 0) {
|
||||
return SCE_OK;
|
||||
}
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy) {
|
||||
if (attr == nullptr || *attr == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
if (policy != SCHED_OTHER) {
|
||||
BREAKPOINT(); // invest if policy is other and if winpthreadlibrary support it
|
||||
}
|
||||
|
||||
(*attr)->policy = policy;
|
||||
|
||||
int result = pthread_attr_setschedpolicy(&(*attr)->pth_attr, policy);
|
||||
|
||||
if (result == 0) {
|
||||
return SCE_OK;
|
||||
}
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
}; // namespace HLE::Libs::LibKernel::ThreadManagement
|
42
src/core/PS4/HLE/Kernel/ThreadManagement.h
Normal file
42
src/core/PS4/HLE/Kernel/ThreadManagement.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
#define _TIMESPEC_DEFINED
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include "../../../../types.h"
|
||||
#include <string>
|
||||
|
||||
namespace HLE::Libs::LibKernel::ThreadManagement {
|
||||
|
||||
struct PthreadAttrInternal;
|
||||
|
||||
using SceKernelSchedParam = ::sched_param;
|
||||
using ScePthreadAttr = PthreadAttrInternal*;
|
||||
|
||||
struct PthreadInternal {
|
||||
u08 reserved[4096];
|
||||
std::string name;
|
||||
pthread_t pth;
|
||||
ScePthreadAttr attr;
|
||||
};
|
||||
struct PthreadAttrInternal {
|
||||
u08 reserved[64];
|
||||
u64 affinity;
|
||||
size_t guard_size;
|
||||
int policy;
|
||||
bool detached;
|
||||
pthread_attr_t pth_attr;
|
||||
};
|
||||
|
||||
class PThreadCxt {};
|
||||
|
||||
void Pthread_Init_Self_MainThread();
|
||||
|
||||
//HLE FUNCTIONS
|
||||
int scePthreadAttrInit(ScePthreadAttr* attr);
|
||||
int scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachstate);
|
||||
int scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inheritSched);
|
||||
int scePthreadAttrSetschedparam(ScePthreadAttr* attr, const SceKernelSchedParam* param);
|
||||
int scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy);
|
||||
|
||||
} // namespace HLE::Libs::LibKernel::ThreadManagement
|
13
src/core/PS4/HLE/Kernel/cpu_management.cpp
Normal file
13
src/core/PS4/HLE/Kernel/cpu_management.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "cpu_management.h"
|
||||
#include "Util/config.h"
|
||||
#include <Util/log.h>
|
||||
#include <core/PS4/HLE/Libs.h>
|
||||
|
||||
namespace HLE::Libs::LibKernel::CPUManagement {
|
||||
int PS4_SYSV_ABI sceKernelIsNeoMode() {
|
||||
PRINT_FUNCTION_NAME();
|
||||
bool isNeo = Config::isNeoMode();
|
||||
return isNeo ? 1 : 0;
|
||||
}
|
||||
|
||||
}; // namespace HLE::Libs::LibKernel::CPUManagement
|
6
src/core/PS4/HLE/Kernel/cpu_management.h
Normal file
6
src/core/PS4/HLE/Kernel/cpu_management.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
#include <types.h>
|
||||
|
||||
namespace HLE::Libs::LibKernel::CPUManagement {
|
||||
int PS4_SYSV_ABI sceKernelIsNeoMode();
|
||||
};
|
66
src/core/PS4/HLE/Kernel/event_queues.cpp
Normal file
66
src/core/PS4/HLE/Kernel/event_queues.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "event_queues.h"
|
||||
|
||||
#include <core/PS4/HLE/ErrorCodes.h>
|
||||
#include <core/PS4/HLE/Libs.h>
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
|
||||
namespace HLE::Libs::LibKernel::EventQueues {
|
||||
constexpr bool log_file_equeues = true; // disable it to disable logging
|
||||
|
||||
int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
|
||||
if (eq == nullptr) {
|
||||
LOG_TRACE_IF(log_file_equeues, "sceKernelCreateEqueue returned SCE_KERNEL_ERROR_EINVAL eq invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (name == nullptr) {
|
||||
LOG_TRACE_IF(log_file_equeues, "sceKernelCreateEqueue returned SCE_KERNEL_ERROR_EFAULT name invalid\n");
|
||||
return SCE_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
if (name == NULL) {
|
||||
LOG_TRACE_IF(log_file_equeues, "sceKernelCreateEqueue returned SCE_KERNEL_ERROR_EINVAL name is null\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
if (strlen(name) > 31) { // max is 32 including null terminator
|
||||
LOG_TRACE_IF(log_file_equeues, "sceKernelCreateEqueue returned SCE_KERNEL_ERROR_ENAMETOOLONG name size exceeds 32 bytes\n");
|
||||
return SCE_KERNEL_ERROR_ENAMETOOLONG;
|
||||
}
|
||||
*eq = new Kernel::Objects::EqueueInternal;
|
||||
|
||||
(*eq)->setName(std::string(name));
|
||||
|
||||
LOG_INFO_IF(log_file_equeues, "sceKernelCreateEqueue created with name \"{}\"\n", name);
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, HLE::Kernel::Objects::SceKernelEvent* ev, int num, int* out, SceKernelUseconds* timo) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
|
||||
if (eq == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
if (ev == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
|
||||
if (num < 1) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (timo == nullptr) { // wait until an event arrives without timing out
|
||||
*out = eq->waitForEvents(ev, num, 0);
|
||||
}
|
||||
if (timo != nullptr) {
|
||||
if (*timo == 0) {//only events that have already arrived at the time of this function call can be received
|
||||
BREAKPOINT();
|
||||
} else { // wait until an event arrives with timing out
|
||||
BREAKPOINT();
|
||||
}
|
||||
}
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
}; // namespace HLE::Libs::LibKernel::EventQueues
|
14
src/core/PS4/HLE/Kernel/event_queues.h
Normal file
14
src/core/PS4/HLE/Kernel/event_queues.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <types.h>
|
||||
|
||||
#include "Objects/event_queue.h"
|
||||
|
||||
namespace HLE::Libs::LibKernel::EventQueues {
|
||||
using SceKernelUseconds = u32;
|
||||
using SceKernelEqueue = Kernel::Objects::EqueueInternal*;
|
||||
|
||||
int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name);
|
||||
int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, HLE::Kernel::Objects::SceKernelEvent* ev, int num, int* out, SceKernelUseconds *timo);
|
||||
|
||||
}; // namespace HLE::Libs::LibKernel::EventQueues
|
129
src/core/PS4/HLE/Kernel/memory_management.cpp
Normal file
129
src/core/PS4/HLE/Kernel/memory_management.cpp
Normal file
|
@ -0,0 +1,129 @@
|
|||
#include "memory_management.h"
|
||||
|
||||
#include <core/PS4/GPU/gpu_memory.h>
|
||||
#include <core/virtual_memory.h>
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
|
||||
#include <bit>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "Emulator/Util/singleton.h"
|
||||
#include "../ErrorCodes.h"
|
||||
#include "../Libs.h"
|
||||
#include "Objects/physical_memory.h"
|
||||
|
||||
namespace HLE::Libs::LibKernel::MemoryManagement {
|
||||
|
||||
constexpr bool log_file_memory = true; // disable it to disable logging
|
||||
|
||||
bool isPowerOfTwo(u64 n) { return std::popcount(n) == 1; }
|
||||
|
||||
bool is16KBAligned(u64 n) { return ((n % (16ull * 1024) == 0)); }
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
|
||||
PRINT_FUNCTION_NAME();
|
||||
return SCE_KERNEL_MAIN_DMEM_SIZE;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, u64 alignment, int memoryType, s64* physAddrOut) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
|
||||
if (searchStart < 0 || searchEnd <= searchStart) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EINVAL searchStart,searchEnd invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
bool isInRange = (searchStart < len && searchEnd > len);
|
||||
if (len <= 0 || !is16KBAligned(len) || !isInRange) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EINVAL memory range invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if ((alignment != 0 || is16KBAligned(alignment)) && !isPowerOfTwo(alignment)) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EINVAL alignment invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (physAddrOut == nullptr) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EINVAL physAddrOut is null\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
auto memtype = magic_enum::enum_cast<MemoryTypes>(memoryType);
|
||||
|
||||
LOG_INFO_IF(log_file_memory, "search_start = {}\n", log_hex_full(searchStart));
|
||||
LOG_INFO_IF(log_file_memory, "search_end = {}\n", log_hex_full(searchEnd));
|
||||
LOG_INFO_IF(log_file_memory, "len = {}\n", log_hex_full(len));
|
||||
LOG_INFO_IF(log_file_memory, "alignment = {}\n", log_hex_full(alignment));
|
||||
LOG_INFO_IF(log_file_memory, "memory_type = {}\n", magic_enum::enum_name(memtype.value()));
|
||||
|
||||
u64 physical_addr = 0;
|
||||
auto* physical_memory = singleton<HLE::Kernel::Objects::PhysicalMemory>::instance();
|
||||
if (!physical_memory->Alloc(searchStart, searchEnd, len, alignment, &physical_addr, memoryType)) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EAGAIN can't allocate physical memory\n");
|
||||
return SCE_KERNEL_ERROR_EAGAIN;
|
||||
}
|
||||
*physAddrOut = static_cast<s64>(physical_addr);
|
||||
LOG_INFO_IF(true, "physAddrOut = {}\n", log_hex_full(physical_addr));
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, s64 directMemoryStart, u64 alignment) {
|
||||
PRINT_FUNCTION_NAME();
|
||||
if (len == 0 || !is16KBAligned(len)) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelMapDirectMemory returned SCE_KERNEL_ERROR_EINVAL len invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (!is16KBAligned(directMemoryStart)) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelMapDirectMemory returned SCE_KERNEL_ERROR_EINVAL directMemoryStart invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (alignment != 0) {
|
||||
if ((!isPowerOfTwo(alignment) && !is16KBAligned(alignment))) {
|
||||
LOG_TRACE_IF(log_file_memory, "sceKernelMapDirectMemory returned SCE_KERNEL_ERROR_EINVAL alignment invalid\n");
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO_IF(log_file_memory, "len = {}\n", log_hex_full(len));
|
||||
LOG_INFO_IF(log_file_memory, "prot = {}\n", log_hex_full(prot));
|
||||
LOG_INFO_IF(log_file_memory, "flags = {}\n", log_hex_full(flags));
|
||||
LOG_INFO_IF(log_file_memory, "directMemoryStart = {}\n", log_hex_full(directMemoryStart));
|
||||
LOG_INFO_IF(log_file_memory, "alignment = {}\n", log_hex_full(alignment));
|
||||
|
||||
VirtualMemory::MemoryMode cpu_mode = VirtualMemory::MemoryMode::NoAccess;
|
||||
GPU::MemoryMode gpu_mode = GPU::MemoryMode::NoAccess;
|
||||
|
||||
switch (prot) {
|
||||
case 0x32:
|
||||
case 0x33: // SCE_KERNEL_PROT_CPU_READ|SCE_KERNEL_PROT_CPU_WRITE|SCE_KERNEL_PROT_GPU_READ|SCE_KERNEL_PROT_GPU_ALL
|
||||
cpu_mode = VirtualMemory::MemoryMode::ReadWrite;
|
||||
gpu_mode = GPU::MemoryMode::ReadWrite;
|
||||
break;
|
||||
default: BREAKPOINT();
|
||||
}
|
||||
|
||||
auto in_addr = reinterpret_cast<u64>(*addr);
|
||||
u64 out_addr = 0;
|
||||
|
||||
if (flags == 0) {
|
||||
out_addr = VirtualMemory::memory_alloc_aligned(in_addr, len, cpu_mode, alignment);
|
||||
}
|
||||
LOG_INFO_IF(log_file_memory, "in_addr = {}\n", log_hex_full(in_addr));
|
||||
LOG_INFO_IF(log_file_memory, "out_addr = {}\n", log_hex_full(out_addr));
|
||||
|
||||
*addr = reinterpret_cast<void*>(out_addr); // return out_addr to first functions parameter
|
||||
|
||||
if (out_addr == 0) {
|
||||
return SCE_KERNEL_ERROR_ENOMEM;
|
||||
}
|
||||
|
||||
auto* physical_memory = singleton<HLE::Kernel::Objects::PhysicalMemory>::instance();
|
||||
if (!physical_memory->Map(out_addr, directMemoryStart, len, prot, cpu_mode, gpu_mode)) {
|
||||
BREAKPOINT();
|
||||
}
|
||||
|
||||
if (gpu_mode != GPU::MemoryMode::NoAccess) {
|
||||
GPU::memorySetAllocArea(out_addr, len);
|
||||
}
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
} // namespace HLE::Libs::LibKernel::MemoryManagement
|
36
src/core/PS4/HLE/Kernel/memory_management.h
Normal file
36
src/core/PS4/HLE/Kernel/memory_management.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <types.h>
|
||||
|
||||
// constants
|
||||
|
||||
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5376_MB; // ~ 6GB
|
||||
|
||||
namespace HLE::Libs::LibKernel::MemoryManagement {
|
||||
|
||||
// memory types
|
||||
|
||||
enum MemoryTypes : u32 {
|
||||
SCE_KERNEL_WB_ONION = 0, // write - back mode (Onion bus)
|
||||
SCE_KERNEL_WC_GARLIC = 3, // write - combining mode (Garlic bus)
|
||||
SCE_KERNEL_WB_GARLIC = 10 // write - back mode (Garlic bus)
|
||||
};
|
||||
|
||||
enum MemoryFlags : u32 {
|
||||
SCE_KERNEL_MAP_FIXED = 0x0010, // Fixed
|
||||
SCE_KERNEL_MAP_NO_OVERWRITE = 0x0080,
|
||||
SCE_KERNEL_MAP_NO_COALESCE = 0x400000
|
||||
};
|
||||
enum MemoryProtection : u32 {
|
||||
SCE_KERNEL_PROT_CPU_READ = 0x01, // Permit reads from the CPU
|
||||
SCE_KERNEL_PROT_CPU_RW = 0x02, // Permit reads/writes from the CPU
|
||||
SCE_KERNEL_PROT_CPU_WRITE = 0x02, // Permit reads/writes from the CPU (same)
|
||||
SCE_KERNEL_PROT_GPU_READ = 0x10, // Permit reads from the GPU
|
||||
SCE_KERNEL_PROT_GPU_WRITE = 0x20, // Permit writes from the GPU
|
||||
SCE_KERNEL_PROT_GPU_RW = 0x30 // Permit reads/writes from the GPU
|
||||
};
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize();
|
||||
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, u64 alignment, int memoryType, s64* physAddrOut);
|
||||
int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, s64 directMemoryStart, u64 alignment);
|
||||
}; // namespace HLE::Libs::LibKernel::MemoryManagement
|
98
src/core/PS4/HLE/LibC.cpp
Normal file
98
src/core/PS4/HLE/LibC.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
#include "LibC.h"
|
||||
|
||||
#include <debug.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "../Loader/Elf.h"
|
||||
#include "Emulator/HLE/Libraries/LibC/libc.h"
|
||||
#include "Emulator/HLE/Libraries/LibC/libc_cxa.h"
|
||||
#include "ErrorCodes.h"
|
||||
#include "Libs.h"
|
||||
|
||||
namespace HLE::Libs::LibC {
|
||||
|
||||
static u32 g_need_sceLibc = 1;
|
||||
|
||||
static PS4_SYSV_ABI void init_env() // every game/demo should probably
|
||||
{
|
||||
// dummy no need atm
|
||||
}
|
||||
|
||||
static PS4_SYSV_ABI void catchReturnFromMain(int status) {
|
||||
// dummy
|
||||
}
|
||||
|
||||
static PS4_SYSV_ABI void _Assert() { BREAKPOINT(); }
|
||||
|
||||
PS4_SYSV_ABI int puts(const char* s) {
|
||||
std::puts(s);
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
PS4_SYSV_ABI int rand() { return std::rand(); }
|
||||
|
||||
PS4_SYSV_ABI void _ZdlPv(void* ptr) { std::free(ptr); }
|
||||
PS4_SYSV_ABI void _ZSt11_Xbad_allocv() { BREAKPOINT(); }
|
||||
PS4_SYSV_ABI void _ZSt14_Xlength_errorPKc() { BREAKPOINT(); }
|
||||
PS4_SYSV_ABI void* _Znwm(u64 count) {
|
||||
if (count == 0) {
|
||||
BREAKPOINT();
|
||||
}
|
||||
void* ptr = std::malloc(count);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
float PS4_SYSV_ABI _Fsin(float arg) { return std::sinf(arg); }
|
||||
|
||||
typedef int(PS4_SYSV_ABI* pfunc_QsortCmp)(const void*, const void*);
|
||||
thread_local static pfunc_QsortCmp compair_ps4;
|
||||
|
||||
int qsort_compair(const void* arg1, const void* arg2) { return compair_ps4(arg1, arg2); }
|
||||
|
||||
void PS4_SYSV_ABI qsort(void* ptr, size_t count,size_t size, int(PS4_SYSV_ABI* comp)(const void*, const void*)) {
|
||||
compair_ps4 = comp;
|
||||
std::qsort(ptr, count, size, qsort_compair);
|
||||
}
|
||||
|
||||
void LibC_Register(SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("bzQExy189ZI", "libc", 1, "libc", 1, 1, init_env);
|
||||
LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::Cxa::__cxa_guard_acquire);
|
||||
LIB_FUNCTION("9rAeANT2tyE", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::Cxa::__cxa_guard_release);
|
||||
LIB_FUNCTION("2emaaluWzUw", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::Cxa::__cxa_guard_abort);
|
||||
LIB_FUNCTION("DfivPArhucg", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::memcmp);
|
||||
LIB_FUNCTION("Q3VBxCXhUHs", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::memcpy);
|
||||
LIB_FUNCTION("8zTFvBIAIN8", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::memset);
|
||||
LIB_FUNCTION("XKRegsFpEpk", "libc", 1, "libc", 1, 1, catchReturnFromMain);
|
||||
LIB_FUNCTION("uMei1W9uyNo", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::exit);
|
||||
LIB_FUNCTION("8G2LB+A3rzg", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::atexit);
|
||||
LIB_FUNCTION("-QgqOT5u2Vk", "libc", 1, "libc", 1, 1, _Assert);
|
||||
LIB_FUNCTION("hcuQgD53UxM", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::printf);
|
||||
LIB_FUNCTION("Q2V+iqvjgC0", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::vsnprintf);
|
||||
LIB_FUNCTION("YQ0navp+YIc", "libc", 1, "libc", 1, 1, puts);
|
||||
LIB_FUNCTION("cpCOXWMgha0", "libc", 1, "libc", 1, 1, rand);
|
||||
LIB_FUNCTION("ZtjspkJQ+vw", "libc", 1, "libc", 1, 1, _Fsin);
|
||||
LIB_FUNCTION("AEJdIVZTEmo", "libc", 1, "libc", 1, 1, qsort);
|
||||
LIB_FUNCTION("Ovb2dSJOAuE", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::strcmp);
|
||||
LIB_FUNCTION("gQX+4GDQjpM", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::malloc);
|
||||
LIB_FUNCTION("tIhsqj0qsFE", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::free);
|
||||
LIB_FUNCTION("j4ViWNHEgww", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::strlen);
|
||||
LIB_FUNCTION("6sJWiWSRuqk", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::strncpy);
|
||||
LIB_FUNCTION("+P6FRGH4LfA", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::memmove);
|
||||
LIB_FUNCTION("kiZSXIWd9vg", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::strcpy);
|
||||
LIB_FUNCTION("Ls4tzzhimqQ", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::strcat);
|
||||
LIB_FUNCTION("EH-x713A99c", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::atan2f);
|
||||
LIB_FUNCTION("QI-x0SL8jhw", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::acosf);
|
||||
LIB_FUNCTION("ZE6RNL+eLbk", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::tanf);
|
||||
LIB_FUNCTION("GZWjF-YIFFk", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::asinf);
|
||||
LIB_FUNCTION("9LCjpWyQ5Zc", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::pow);
|
||||
LIB_FUNCTION("cCXjU72Z0Ow", "libc", 1, "libc", 1, 1, Emulator::HLE::Libraries::LibC::_Sin);
|
||||
|
||||
LIB_OBJ("P330P3dFF68", "libc", 1, "libc", 1, 1, &HLE::Libs::LibC::g_need_sceLibc);
|
||||
|
||||
LIB_FUNCTION("z+P+xCnWLBk", "libc", 1, "libc", 1, 1, _ZdlPv);
|
||||
LIB_FUNCTION("eT2UsmTewbU", "libc", 1, "libc", 1, 1, _ZSt11_Xbad_allocv);
|
||||
LIB_FUNCTION("tQIo+GIPklo", "libc", 1, "libc", 1, 1, _ZSt14_Xlength_errorPKc);
|
||||
LIB_FUNCTION("fJnpuVVBbKk", "libc", 1, "libc", 1, 1, _Znwm);
|
||||
}
|
||||
|
||||
}; // namespace HLE::Libs::LibC
|
13
src/core/PS4/HLE/LibC.h
Normal file
13
src/core/PS4/HLE/LibC.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include "../Loader/SymbolsResolver.h"
|
||||
|
||||
namespace HLE::Libs::LibC {
|
||||
|
||||
void LibC_Register(SymbolsResolver* sym);
|
||||
|
||||
//functions
|
||||
static PS4_SYSV_ABI void init_env();
|
||||
static PS4_SYSV_ABI void _Assert();
|
||||
static PS4_SYSV_ABI void catchReturnFromMain(int status);
|
||||
|
||||
};
|
49
src/core/PS4/HLE/LibKernel.cpp
Normal file
49
src/core/PS4/HLE/LibKernel.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include "LibKernel.h"
|
||||
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "Emulator/Util/singleton.h"
|
||||
#include "../Loader/Elf.h"
|
||||
#include "Kernel/Objects/physical_memory.h"
|
||||
#include "Kernel/cpu_management.h"
|
||||
#include "Kernel/event_queues.h"
|
||||
#include "Kernel/memory_management.h"
|
||||
#include "Libs.h"
|
||||
#include "core/hle/libraries/libkernel/file_system.h"
|
||||
#include "core/hle/libraries/libkernel/time_management.h"
|
||||
|
||||
namespace HLE::Libs::LibKernel {
|
||||
|
||||
static u64 g_stack_chk_guard = 0xDEADBEEF54321ABC; // dummy return
|
||||
|
||||
int32_t PS4_SYSV_ABI sceKernelReleaseDirectMemory(off_t start, size_t len) {
|
||||
BREAKPOINT();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PS4_SYSV_ABI void stack_chk_fail() { BREAKPOINT(); }
|
||||
|
||||
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { BREAKPOINT(); }
|
||||
void LibKernel_Register(SymbolsResolver* sym) {
|
||||
// obj
|
||||
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &HLE::Libs::LibKernel::g_stack_chk_guard);
|
||||
// memory
|
||||
LIB_FUNCTION("rTXw65xmLIA", "libkernel", 1, "libkernel", 1, 1, MemoryManagement::sceKernelAllocateDirectMemory);
|
||||
LIB_FUNCTION("pO96TwzOm5E", "libkernel", 1, "libkernel", 1, 1, MemoryManagement::sceKernelGetDirectMemorySize);
|
||||
LIB_FUNCTION("L-Q3LEjIbgA", "libkernel", 1, "libkernel", 1, 1, MemoryManagement::sceKernelMapDirectMemory);
|
||||
LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory);
|
||||
LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap);
|
||||
// equeue
|
||||
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, EventQueues::sceKernelCreateEqueue);
|
||||
LIB_FUNCTION("fzyMKs9kim0", "libkernel", 1, "libkernel", 1, 1, EventQueues::sceKernelWaitEqueue);
|
||||
// misc
|
||||
LIB_FUNCTION("WslcK1FQcGI", "libkernel", 1, "libkernel", 1, 1, CPUManagement::sceKernelIsNeoMode);
|
||||
LIB_FUNCTION("Ou3iL1abvng", "libkernel", 1, "libkernel", 1, 1, stack_chk_fail);
|
||||
|
||||
Core::Libraries::LibKernel::fileSystemSymbolsRegister(sym);
|
||||
Core::Libraries::LibKernel::timeSymbolsRegister(sym);
|
||||
}
|
||||
|
||||
}; // namespace HLE::Libs::LibKernel
|
10
src/core/PS4/HLE/LibKernel.h
Normal file
10
src/core/PS4/HLE/LibKernel.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include "../Loader/SymbolsResolver.h"
|
||||
|
||||
namespace HLE::Libs::LibKernel {
|
||||
|
||||
void LibKernel_Register(SymbolsResolver* sym);
|
||||
|
||||
// functions
|
||||
|
||||
int32_t PS4_SYSV_ABI sceKernelReleaseDirectMemory(off_t start, size_t len);
|
||||
}; // namespace HLE::Libs::LibKernel
|
27
src/core/PS4/HLE/LibSceGnmDriver.cpp
Normal file
27
src/core/PS4/HLE/LibSceGnmDriver.cpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#include "LibSceGnmDriver.h"
|
||||
#include "Libs.h"
|
||||
#include "../Loader/Elf.h"
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
#include <core/PS4/GPU/gpu_memory.h>
|
||||
#include <emulator.h>
|
||||
|
||||
namespace HLE::Libs::LibSceGnmDriver {
|
||||
|
||||
int32_t sceGnmSubmitDone()
|
||||
{
|
||||
PRINT_DUMMY_FUNCTION_NAME();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sceGnmFlushGarlic() { PRINT_FUNCTION_NAME();
|
||||
GPU::flushGarlic(Emu::getGraphicCtx());
|
||||
}
|
||||
|
||||
void LibSceGnmDriver_Register(SymbolsResolver* sym)
|
||||
{
|
||||
LIB_FUNCTION("yvZ73uQUqrk", "libSceGnmDriver", 1, "libSceGnmDriver", 1, 1, sceGnmSubmitDone);
|
||||
LIB_FUNCTION("iBt3Oe00Kvc", "libSceGnmDriver", 1, "libSceGnmDriver", 1, 1, sceGnmFlushGarlic);
|
||||
}
|
||||
|
||||
};
|
9
src/core/PS4/HLE/LibSceGnmDriver.h
Normal file
9
src/core/PS4/HLE/LibSceGnmDriver.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#include "../Loader/SymbolsResolver.h"
|
||||
|
||||
namespace HLE::Libs::LibSceGnmDriver {
|
||||
|
||||
void LibSceGnmDriver_Register(SymbolsResolver* sym);
|
||||
int32_t sceGnmSubmitDone();
|
||||
void sceGnmFlushGarlic();
|
||||
}; // namespace HLE::Libs::LibSceGnmDriver
|
22
src/core/PS4/HLE/Libs.cpp
Normal file
22
src/core/PS4/HLE/Libs.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "Libs.h"
|
||||
|
||||
#include "LibC.h"
|
||||
#include "LibKernel.h"
|
||||
#include "LibSceGnmDriver.h"
|
||||
#include <core/PS4/HLE/Graphics/video_out.h>
|
||||
#include "core/hle/libraries/libuserservice/user_service.h"
|
||||
#include "core/hle/libraries/libpad/pad.h"
|
||||
#include <core/hle/libraries/libsystemservice/system_service.h>
|
||||
|
||||
namespace HLE::Libs {
|
||||
|
||||
void Init_HLE_Libs(SymbolsResolver *sym) {
|
||||
LibC::LibC_Register(sym);
|
||||
LibKernel::LibKernel_Register(sym);
|
||||
Graphics::VideoOut::videoOutRegisterLib(sym);
|
||||
LibSceGnmDriver::LibSceGnmDriver_Register(sym);
|
||||
Core::Libraries::LibUserService::userServiceSymbolsRegister(sym);
|
||||
Core::Libraries::LibPad::padSymbolsRegister(sym);
|
||||
Core::Libraries::LibSystemService::systemServiceSymbolsRegister(sym);
|
||||
}
|
||||
} // namespace HLE::Libs
|
43
src/core/PS4/HLE/Libs.h
Normal file
43
src/core/PS4/HLE/Libs.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
#include "../Loader/SymbolsResolver.h"
|
||||
#include <core/PS4/Loader/Elf.h>
|
||||
|
||||
#define LIB_FUNCTION(nid, lib, libversion, mod, moduleVersionMajor, moduleVersionMinor, function) \
|
||||
{\
|
||||
SymbolRes sr{}; \
|
||||
sr.name = nid; \
|
||||
sr.library = lib; \
|
||||
sr.library_version = libversion;\
|
||||
sr.module = mod;\
|
||||
sr.module_version_major = moduleVersionMajor;\
|
||||
sr.module_version_minor = moduleVersionMinor;\
|
||||
sr.type = STT_FUN;\
|
||||
auto func = reinterpret_cast<u64>(function);\
|
||||
sym->AddSymbol(sr, func);\
|
||||
}
|
||||
|
||||
#define LIB_OBJ(nid, lib, libversion, mod, moduleVersionMajor, moduleVersionMinor, function) \
|
||||
{ \
|
||||
SymbolRes sr{}; \
|
||||
sr.name = nid; \
|
||||
sr.library = lib; \
|
||||
sr.library_version = libversion; \
|
||||
sr.module = mod; \
|
||||
sr.module_version_major = moduleVersionMajor; \
|
||||
sr.module_version_minor = moduleVersionMinor; \
|
||||
sr.type = STT_OBJECT; \
|
||||
auto func = reinterpret_cast<u64>(function); \
|
||||
sym->AddSymbol(sr, func); \
|
||||
}
|
||||
|
||||
#define PRINT_FUNCTION_NAME() \
|
||||
{ \
|
||||
LOG_INFO_IF(true, "{}()\n", __func__); \
|
||||
}
|
||||
|
||||
#define PRINT_DUMMY_FUNCTION_NAME() \
|
||||
{ LOG_WARN_IF(true, "dummy {}()\n", __func__); }
|
||||
|
||||
namespace HLE::Libs {
|
||||
void Init_HLE_Libs(SymbolsResolver* sym);
|
||||
}
|
12
src/core/PS4/HLE/UserManagement/UsrMngCodes.h
Normal file
12
src/core/PS4/HLE/UserManagement/UsrMngCodes.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
//constants
|
||||
|
||||
constexpr int SCE_USER_SERVICE_MAX_LOGIN_USERS = 4; //max users logged in at once
|
||||
constexpr int SCE_USER_SERVICE_MAX_USER_NAME_LENGTH = 16;//Max length for user name
|
||||
|
||||
constexpr int SCE_USER_SERVICE_USER_ID_INVALID = -1;//invalid user ID
|
||||
constexpr int SCE_USER_SERVICE_USER_ID_SYSTEM = 255; //generic id for device
|
||||
constexpr int SCE_USER_SERVICE_USER_ID_EVERYONE = 254; // generic id for user (mostly used in common dialogs)
|
||||
|
||||
|
665
src/core/PS4/Linker.cpp
Normal file
665
src/core/PS4/Linker.cpp
Normal file
|
@ -0,0 +1,665 @@
|
|||
#include "Linker.h"
|
||||
#include "../virtual_memory.h"
|
||||
#include <Util/log.h>
|
||||
#include <fmt/core.h>
|
||||
#include "Zydis.h"
|
||||
#include <Util/string_util.h>
|
||||
#include "Util/aerolib.h"
|
||||
#include "Loader/SymbolsResolver.h"
|
||||
#include "HLE/Kernel/ThreadManagement.h"
|
||||
#include "Stubs.h"
|
||||
|
||||
constexpr bool debug_loader = true;
|
||||
|
||||
static u64 g_load_addr = SYSTEM_RESERVED + CODE_BASE_OFFSET;
|
||||
|
||||
static u64 get_aligned_size(const elf_program_header& phdr)
|
||||
{
|
||||
return (phdr.p_align != 0 ? (phdr.p_memsz + (phdr.p_align - 1)) & ~(phdr.p_align - 1) : phdr.p_memsz);
|
||||
}
|
||||
|
||||
static u64 calculate_base_size(const elf_header& ehdr, std::span<const elf_program_header> phdr)
|
||||
{
|
||||
u64 base_size = 0;
|
||||
for (u16 i = 0; i < ehdr.e_phnum; i++)
|
||||
{
|
||||
if (phdr[i].p_memsz != 0 && (phdr[i].p_type == PT_LOAD || phdr[i].p_type == PT_SCE_RELRO))
|
||||
{
|
||||
u64 last_addr = phdr[i].p_vaddr + get_aligned_size(phdr[i]);
|
||||
if (last_addr > base_size)
|
||||
{
|
||||
base_size = last_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return base_size;
|
||||
}
|
||||
|
||||
static std::string encodeId(u64 nVal)
|
||||
{
|
||||
std::string enc;
|
||||
const char pCodes[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-";
|
||||
if (nVal < 0x40u)
|
||||
{
|
||||
enc += pCodes[nVal];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nVal < 0x1000u)
|
||||
{
|
||||
enc += pCodes[static_cast<u16>(nVal >> 6u) & 0x3fu];
|
||||
enc += pCodes[nVal & 0x3fu];
|
||||
}
|
||||
else
|
||||
{
|
||||
enc += pCodes[static_cast<u16>(nVal >> 12u) & 0x3fu];
|
||||
enc += pCodes[static_cast<u16>(nVal >> 6u) & 0x3fu];
|
||||
enc += pCodes[nVal & 0x3fu];
|
||||
}
|
||||
}
|
||||
return enc;
|
||||
}
|
||||
|
||||
Linker::Linker() = default;
|
||||
|
||||
Linker::~Linker() = default;
|
||||
|
||||
Module* Linker::LoadModule(const std::string& elf_name)
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
auto& m = m_modules.emplace_back();
|
||||
m.linker = this;
|
||||
m.elf.Open(elf_name);
|
||||
|
||||
if (m.elf.isElfFile()) {
|
||||
LoadModuleToMemory(&m);
|
||||
LoadDynamicInfo(&m);
|
||||
LoadSymbols(&m);
|
||||
Relocate(&m);
|
||||
} else {
|
||||
m_modules.pop_back();
|
||||
return nullptr; // It is not a valid elf file //TODO check it why!
|
||||
}
|
||||
|
||||
return &m;
|
||||
}
|
||||
|
||||
Module* Linker::FindModule(/*u32 id*/)
|
||||
{
|
||||
// TODO atm we only have 1 module so we don't need to iterate on vector
|
||||
if (m_modules.empty()) [[unlikely]] {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_modules[0];
|
||||
}
|
||||
|
||||
void Linker::LoadModuleToMemory(Module* m)
|
||||
{
|
||||
//get elf header, program header
|
||||
const auto elf_header = m->elf.GetElfHeader();
|
||||
const auto elf_pheader = m->elf.GetProgramHeader();
|
||||
|
||||
u64 base_size = calculate_base_size(elf_header, elf_pheader);
|
||||
m->aligned_base_size = (base_size & ~(static_cast<u64>(0x1000) - 1)) + 0x1000;//align base size to 0x1000 block size (TODO is that the default block size or it can be changed?
|
||||
|
||||
m->base_virtual_addr = VirtualMemory::memory_alloc(g_load_addr, m->aligned_base_size, VirtualMemory::MemoryMode::ExecuteReadWrite);
|
||||
|
||||
LOG_INFO_IF(debug_loader, "====Load Module to Memory ========\n");
|
||||
LOG_INFO_IF(debug_loader, "base_virtual_addr ......: {:#018x}\n", m->base_virtual_addr);
|
||||
LOG_INFO_IF(debug_loader, "base_size ..............: {:#018x}\n", base_size);
|
||||
LOG_INFO_IF(debug_loader, "aligned_base_size ......: {:#018x}\n", m->aligned_base_size);
|
||||
|
||||
for (u16 i = 0; i < elf_header.e_phnum; i++)
|
||||
{
|
||||
switch (elf_pheader[i].p_type)
|
||||
{
|
||||
case PT_LOAD:
|
||||
case PT_SCE_RELRO:
|
||||
if (elf_pheader[i].p_memsz != 0)
|
||||
{
|
||||
u64 segment_addr = elf_pheader[i].p_vaddr + m->base_virtual_addr;
|
||||
u64 segment_file_size = elf_pheader[i].p_filesz;
|
||||
u64 segment_memory_size = get_aligned_size(elf_pheader[i]);
|
||||
auto segment_mode = m->elf.ElfPheaderFlagsStr(elf_pheader[i].p_flags);
|
||||
LOG_INFO_IF(debug_loader, "program header = [{}] type = {}\n",i,m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
|
||||
LOG_INFO_IF(debug_loader, "segment_addr ..........: {:#018x}\n", segment_addr);
|
||||
LOG_INFO_IF(debug_loader, "segment_file_size .....: {}\n", segment_file_size);
|
||||
LOG_INFO_IF(debug_loader, "segment_memory_size ...: {}\n", segment_memory_size);
|
||||
LOG_INFO_IF(debug_loader, "segment_mode ..........: {}\n", segment_mode);
|
||||
|
||||
m->elf.LoadSegment(segment_addr, elf_pheader[i].p_offset, segment_file_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR_IF(debug_loader, "p_memsz==0 in type {}\n", m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
|
||||
}
|
||||
break;
|
||||
case PT_DYNAMIC:
|
||||
if (elf_pheader[i].p_filesz != 0)
|
||||
{
|
||||
m->m_dynamic.resize(elf_pheader[i].p_filesz);
|
||||
m->elf.LoadSegment(reinterpret_cast<u64>(m->m_dynamic.data()), elf_pheader[i].p_offset, elf_pheader[i].p_filesz);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR_IF(debug_loader, "p_filesz==0 in type {}\n", m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
|
||||
}
|
||||
break;
|
||||
case PT_SCE_DYNLIBDATA:
|
||||
if (elf_pheader[i].p_filesz != 0)
|
||||
{
|
||||
m->m_dynamic_data.resize(elf_pheader[i].p_filesz);
|
||||
m->elf.LoadSegment(reinterpret_cast<u64>(m->m_dynamic_data.data()), elf_pheader[i].p_offset, elf_pheader[i].p_filesz);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR_IF(debug_loader, "p_filesz==0 in type {}\n", m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR_IF(debug_loader, "Unimplemented type {}\n", m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
|
||||
}
|
||||
}
|
||||
LOG_INFO_IF(debug_loader, "program entry addr ..........: {:#018x}\n", m->elf.GetElfEntry() + m->base_virtual_addr);
|
||||
|
||||
auto* rt1 = reinterpret_cast<uint8_t*>(m->elf.GetElfEntry() + m->base_virtual_addr);
|
||||
ZyanU64 runtime_address = m->elf.GetElfEntry() + m->base_virtual_addr;
|
||||
|
||||
// Loop over the instructions in our buffer.
|
||||
ZyanUSize offset = 0;
|
||||
ZydisDisassembledInstruction instruction;
|
||||
while (ZYAN_SUCCESS(ZydisDisassembleIntel(
|
||||
/* machine_mode: */ ZYDIS_MACHINE_MODE_LONG_64,
|
||||
/* runtime_address: */ runtime_address,
|
||||
/* buffer: */ rt1 + offset,
|
||||
/* length: */ sizeof(rt1) - offset,
|
||||
/* instruction: */ &instruction
|
||||
))) {
|
||||
fmt::print("{:#x}" PRIX64 " {}\n", runtime_address, instruction.text);
|
||||
offset += instruction.info.length;
|
||||
runtime_address += instruction.info.length;
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::LoadDynamicInfo(Module* m)
|
||||
{
|
||||
for (const auto* dyn = reinterpret_cast<elf_dynamic*>(m->m_dynamic.data()); dyn->d_tag != DT_NULL; dyn++)
|
||||
{
|
||||
switch (dyn->d_tag)
|
||||
{
|
||||
case DT_SCE_HASH: //Offset of the hash table.
|
||||
m->dynamic_info.hash_table = reinterpret_cast<void*>(m->m_dynamic_data.data() + dyn->d_un.d_ptr);
|
||||
break;
|
||||
case DT_SCE_HASHSZ: //Size of the hash table
|
||||
m->dynamic_info.hash_table_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_SCE_STRTAB://Offset of the string table.
|
||||
m->dynamic_info.str_table = reinterpret_cast<char*>(m->m_dynamic_data.data() + dyn->d_un.d_ptr);
|
||||
break;
|
||||
case DT_SCE_STRSZ: //Size of the string table.
|
||||
m->dynamic_info.str_table_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_SCE_SYMTAB://Offset of the symbol table.
|
||||
m->dynamic_info.symbol_table = reinterpret_cast<elf_symbol*>(m->m_dynamic_data.data() + dyn->d_un.d_ptr);
|
||||
break;
|
||||
case DT_SCE_SYMTABSZ://Size of the symbol table.
|
||||
m->dynamic_info.symbol_table_total_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_INIT:
|
||||
m->dynamic_info.init_virtual_addr = dyn->d_un.d_ptr;
|
||||
break;
|
||||
case DT_FINI:
|
||||
m->dynamic_info.fini_virtual_addr = dyn->d_un.d_ptr;
|
||||
break;
|
||||
case DT_SCE_PLTGOT: //Offset of the global offset table.
|
||||
m->dynamic_info.pltgot_virtual_addr = dyn->d_un.d_ptr;
|
||||
break;
|
||||
case DT_SCE_JMPREL: //Offset of the table containing jump slots.
|
||||
m->dynamic_info.jmp_relocation_table = reinterpret_cast<elf_relocation*>(m->m_dynamic_data.data() + dyn->d_un.d_ptr);
|
||||
break;
|
||||
case DT_SCE_PLTRELSZ: //Size of the global offset table.
|
||||
m->dynamic_info.jmp_relocation_table_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_SCE_PLTREL: //The type of relocations in the relocation table. Should be DT_RELA
|
||||
m->dynamic_info.jmp_relocation_type = dyn->d_un.d_val;
|
||||
if (m->dynamic_info.jmp_relocation_type != DT_RELA)
|
||||
{
|
||||
LOG_WARN_IF(debug_loader, "DT_SCE_PLTREL is NOT DT_RELA should check!");
|
||||
}
|
||||
break;
|
||||
case DT_SCE_RELA: //Offset of the relocation table.
|
||||
m->dynamic_info.relocation_table = reinterpret_cast<elf_relocation*>(m->m_dynamic_data.data() + dyn->d_un.d_ptr);
|
||||
break;
|
||||
case DT_SCE_RELASZ: //Size of the relocation table.
|
||||
m->dynamic_info.relocation_table_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_SCE_RELAENT : //The size of relocation table entries.
|
||||
m->dynamic_info.relocation_table_entries_size = dyn->d_un.d_val;
|
||||
if (m->dynamic_info.relocation_table_entries_size != 0x18) //this value should always be 0x18
|
||||
{
|
||||
LOG_WARN_IF(debug_loader, "DT_SCE_RELAENT is NOT 0x18 should check!");
|
||||
}
|
||||
break;
|
||||
case DT_INIT_ARRAY:// Address of the array of pointers to initialization functions
|
||||
m->dynamic_info.init_array_virtual_addr = dyn->d_un.d_ptr;
|
||||
break;
|
||||
case DT_FINI_ARRAY: // Address of the array of pointers to termination functions
|
||||
m->dynamic_info.fini_array_virtual_addr = dyn->d_un.d_ptr;
|
||||
break;
|
||||
case DT_INIT_ARRAYSZ://Size in bytes of the array of initialization functions
|
||||
m->dynamic_info.init_array_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_FINI_ARRAYSZ://Size in bytes of the array of terminationfunctions
|
||||
m->dynamic_info.fini_array_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_PREINIT_ARRAY://Address of the array of pointers to pre - initialization functions
|
||||
m->dynamic_info.preinit_array_virtual_addr = dyn->d_un.d_ptr;
|
||||
break;
|
||||
case DT_PREINIT_ARRAYSZ://Size in bytes of the array of pre - initialization functions
|
||||
m->dynamic_info.preinit_array_size = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_SCE_SYMENT: //The size of symbol table entries
|
||||
m->dynamic_info.symbol_table_entries_size = dyn->d_un.d_val;
|
||||
if (m->dynamic_info.symbol_table_entries_size != 0x18) //this value should always be 0x18
|
||||
{
|
||||
LOG_WARN_IF(debug_loader, "DT_SCE_SYMENT is NOT 0x18 should check!");
|
||||
}
|
||||
break;
|
||||
case DT_DEBUG:
|
||||
m->dynamic_info.debug = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_TEXTREL:
|
||||
m->dynamic_info.textrel = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_FLAGS:
|
||||
m->dynamic_info.flags = dyn->d_un.d_val;
|
||||
if (m->dynamic_info.flags != 0x04) //this value should always be DF_TEXTREL (0x04)
|
||||
{
|
||||
LOG_WARN_IF(debug_loader, "DT_FLAGS is NOT 0x04 should check!");
|
||||
}
|
||||
break;
|
||||
case DT_NEEDED://Offset of the library string in the string table to be linked in.
|
||||
if (m->dynamic_info.str_table != nullptr)//in theory this should already be filled from about just make a test case
|
||||
{
|
||||
m->dynamic_info.needed.push_back(m->dynamic_info.str_table + dyn->d_un.d_val);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR_IF(debug_loader, "DT_NEEDED str table is not loaded should check!");
|
||||
}
|
||||
break;
|
||||
case DT_SCE_NEEDED_MODULE:
|
||||
{
|
||||
ModuleInfo info{};
|
||||
info.value = dyn->d_un.d_val;
|
||||
info.name = m->dynamic_info.str_table + info.name_offset;
|
||||
info.enc_id = encodeId(info.id);
|
||||
m->dynamic_info.import_modules.push_back(info);
|
||||
}
|
||||
break;
|
||||
case DT_SCE_IMPORT_LIB:
|
||||
{
|
||||
LibraryInfo info{};
|
||||
info.value = dyn->d_un.d_val;
|
||||
info.name = m->dynamic_info.str_table + info.name_offset;
|
||||
info.enc_id = encodeId(info.id);
|
||||
m->dynamic_info.import_libs.push_back(info);
|
||||
}
|
||||
break;
|
||||
case DT_SCE_FINGERPRINT:
|
||||
//The fingerprint is a 24 byte (0x18) size buffer that contains a unique identifier for the given app.
|
||||
//How exactly this is generated isn't known, however it is not necessary to have a valid fingerprint.
|
||||
//While an invalid fingerprint will cause a warning to be printed to the kernel log, the ELF will still load and run.
|
||||
LOG_INFO_IF(debug_loader, "unsupported DT_SCE_FINGERPRINT value = ..........: {:#018x}\n", dyn->d_un.d_val);
|
||||
break;
|
||||
case DT_SCE_IMPORT_LIB_ATTR:
|
||||
//The upper 32-bits should contain the module index multiplied by 0x10000. The lower 32-bits should be a constant 0x9.
|
||||
LOG_INFO_IF(debug_loader, "unsupported DT_SCE_IMPORT_LIB_ATTR value = ..........: {:#018x}\n", dyn->d_un.d_val);
|
||||
break;
|
||||
case DT_SCE_ORIGINAL_FILENAME:
|
||||
m->dynamic_info.filename = m->dynamic_info.str_table + dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_SCE_MODULE_INFO://probably only useable in shared modules
|
||||
{
|
||||
ModuleInfo info{};
|
||||
info.value = dyn->d_un.d_val;
|
||||
info.name = m->dynamic_info.str_table + info.name_offset;
|
||||
info.enc_id = encodeId(info.id);
|
||||
m->dynamic_info.export_modules.push_back(info);
|
||||
}
|
||||
break;
|
||||
case DT_SCE_MODULE_ATTR:
|
||||
//TODO?
|
||||
LOG_INFO_IF(debug_loader, "unsupported DT_SCE_MODULE_ATTR value = ..........: {:#018x}\n", dyn->d_un.d_val);
|
||||
break;
|
||||
case DT_SCE_EXPORT_LIB:
|
||||
{
|
||||
LibraryInfo info{};
|
||||
info.value = dyn->d_un.d_val;
|
||||
info.name = m->dynamic_info.str_table + info.name_offset;
|
||||
info.enc_id = encodeId(info.id);
|
||||
m->dynamic_info.export_libs.push_back(info);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_INFO_IF(debug_loader, "unsupported dynamic tag ..........: {:#018x}\n", dyn->d_tag);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const ModuleInfo* Linker::FindModule(const Module& m, const std::string& id)
|
||||
{
|
||||
const auto& import_modules = m.dynamic_info.import_modules;
|
||||
int index = 0;
|
||||
for (const auto& mod : import_modules)
|
||||
{
|
||||
if (mod.enc_id.compare(id) == 0)
|
||||
{
|
||||
return &import_modules.at(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
const auto& export_modules = m.dynamic_info.export_modules;
|
||||
index = 0;
|
||||
for (const auto& mod : export_modules)
|
||||
{
|
||||
if (mod.enc_id.compare(id) == 0)
|
||||
{
|
||||
return &export_modules.at(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const LibraryInfo* Linker::FindLibrary(const Module& m, const std::string& id)
|
||||
{
|
||||
const auto& import_libs = m.dynamic_info.import_libs;
|
||||
int index = 0;
|
||||
for (const auto& lib : import_libs)
|
||||
{
|
||||
if (lib.enc_id.compare(id) == 0)
|
||||
{
|
||||
return &import_libs.at(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
const auto& export_libs = m.dynamic_info.export_libs;
|
||||
index = 0;
|
||||
for (const auto& lib : export_libs)
|
||||
{
|
||||
if (lib.enc_id.compare(id) == 0)
|
||||
{
|
||||
return &export_libs.at(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Linker::LoadSymbols(Module* m)
|
||||
{
|
||||
if (m->dynamic_info.symbol_table == nullptr || m->dynamic_info.str_table == nullptr || m->dynamic_info.symbol_table_total_size==0)
|
||||
{
|
||||
LOG_INFO_IF(debug_loader, "Symbol table not found!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto* sym = m->dynamic_info.symbol_table;
|
||||
reinterpret_cast<uint8_t*>(sym) < reinterpret_cast<uint8_t*>(m->dynamic_info.symbol_table) + m->dynamic_info.symbol_table_total_size;
|
||||
sym++)
|
||||
{
|
||||
std::string id = std::string(m->dynamic_info.str_table + sym->st_name);
|
||||
auto ids = StringUtil::split_string(id, '#');
|
||||
if (ids.size() == 3)//symbols are 3 parts name , library , module
|
||||
{
|
||||
const auto* library = FindLibrary(*m, ids.at(1));
|
||||
const auto* module = FindModule(*m, ids.at(2));
|
||||
auto bind = sym->GetBind();
|
||||
auto type = sym->GetType();
|
||||
auto visibility = sym->GetVisibility();
|
||||
if (library != nullptr || module != nullptr)
|
||||
{
|
||||
switch (bind)
|
||||
{
|
||||
case STB_GLOBAL:
|
||||
case STB_WEAK:
|
||||
break;
|
||||
default:
|
||||
LOG_INFO_IF(debug_loader, "Unsupported bind {} for name symbol {} \n", bind,ids.at(0));
|
||||
continue;
|
||||
}
|
||||
switch (type)
|
||||
{
|
||||
case STT_OBJECT:
|
||||
case STT_FUN:
|
||||
break;
|
||||
default:
|
||||
LOG_INFO_IF(debug_loader, "Unsupported type {} for name symbol {} \n", type, ids.at(0));
|
||||
continue;
|
||||
}
|
||||
switch (visibility)
|
||||
{
|
||||
case STV_DEFAULT:
|
||||
break;
|
||||
default:
|
||||
LOG_INFO_IF(debug_loader, "Unsupported visibility {} for name symbol {} \n", visibility, ids.at(0));
|
||||
continue;
|
||||
}
|
||||
//if st_value!=0 then it's export symbol
|
||||
bool is_sym_export = sym->st_value != 0;
|
||||
std::string nidName = "";
|
||||
|
||||
auto aeronid = aerolib::find_by_nid(ids.at(0).c_str());
|
||||
|
||||
if (aeronid != nullptr)
|
||||
{
|
||||
nidName = aeronid->name;
|
||||
}
|
||||
else
|
||||
{
|
||||
nidName = "UNK";
|
||||
}
|
||||
|
||||
SymbolRes sym_r{};
|
||||
sym_r.name = ids.at(0);
|
||||
sym_r.nidName = nidName;
|
||||
sym_r.library = library->name;
|
||||
sym_r.library_version = library->version;
|
||||
sym_r.module = module->name;
|
||||
sym_r.module_version_major = module->version_major;
|
||||
sym_r.module_version_minor = module->version_minor;
|
||||
sym_r.type = type;
|
||||
|
||||
if (is_sym_export)
|
||||
{
|
||||
m->export_sym.AddSymbol(sym_r, sym->st_value + m->base_virtual_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
m->import_sym.AddSymbol(sym_r,0);
|
||||
}
|
||||
|
||||
|
||||
LOG_INFO_IF(debug_loader, "name {} function {} library {} module {} bind {} type {} visibility {}\n", ids.at(0),nidName,library->name, module->name, bind, type, visibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static void relocate(u32 idx, elf_relocation* rel, Module* m, bool isJmpRel) {
|
||||
auto type = rel->GetType();
|
||||
auto symbol = rel->GetSymbol();
|
||||
auto addend = rel->rel_addend;
|
||||
auto* symbolsTlb = m->dynamic_info.symbol_table;
|
||||
auto* namesTlb = m->dynamic_info.str_table;
|
||||
|
||||
u64 rel_value = 0;
|
||||
u64 rel_base_virtual_addr = m->base_virtual_addr;
|
||||
u64 rel_virtual_addr = m->base_virtual_addr + rel->rel_offset;
|
||||
bool rel_isResolved = false;
|
||||
u08 rel_sym_type = 0;
|
||||
std::string rel_name;
|
||||
|
||||
switch (type) {
|
||||
case R_X86_64_RELATIVE:
|
||||
if (symbol != 0) // should be always zero
|
||||
{
|
||||
LOG_INFO_IF(debug_loader, "R_X86_64_RELATIVE symbol not zero = {:#010x}\n", type, symbol);
|
||||
}
|
||||
rel_value = rel_base_virtual_addr + addend;
|
||||
rel_isResolved = true;
|
||||
break;
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_JUMP_SLOT: // similar but addend is not take into account
|
||||
{
|
||||
auto sym = symbolsTlb[symbol];
|
||||
auto sym_bind = sym.GetBind();
|
||||
auto sym_type = sym.GetType();
|
||||
auto sym_visibility = sym.GetVisibility();
|
||||
u64 symbol_vitrual_addr = 0;
|
||||
SymbolRecord symrec{};
|
||||
switch (sym_type) {
|
||||
case STT_FUN: rel_sym_type = 2; break;
|
||||
case STT_OBJECT: rel_sym_type = 1; break;
|
||||
default: LOG_INFO_IF(debug_loader, "unknown symbol type {}\n", sym_type);
|
||||
}
|
||||
if (sym_visibility != 0) // should be zero log if else
|
||||
{
|
||||
LOG_INFO_IF(debug_loader, "symbol visilibity !=0\n");
|
||||
}
|
||||
switch (sym_bind) {
|
||||
case STB_GLOBAL:
|
||||
rel_name = namesTlb + sym.st_name;
|
||||
m->linker->Resolve(rel_name, rel_sym_type, m, &symrec);
|
||||
symbol_vitrual_addr = symrec.virtual_address;
|
||||
rel_isResolved = (symbol_vitrual_addr != 0);
|
||||
|
||||
rel_name = symrec.name;
|
||||
if (type == R_X86_64_JUMP_SLOT) {
|
||||
addend = 0;
|
||||
}
|
||||
rel_value = (rel_isResolved ? symbol_vitrual_addr + addend : 0);
|
||||
if (!rel_isResolved) {
|
||||
LOG_INFO_IF(debug_loader, "R_X86_64_64-R_X86_64_JUMP_SLOT sym_type {} bind STB_GLOBAL symbol : {:#010x}\n", sym_type, symbol);
|
||||
}
|
||||
break;
|
||||
default: LOG_INFO_IF(debug_loader, "UNK bind {}\n", sym_bind);
|
||||
}
|
||||
|
||||
} break;
|
||||
default: LOG_INFO_IF(debug_loader, "UNK type {:#010x} rel symbol : {:#010x}\n", type, symbol);
|
||||
}
|
||||
|
||||
if (rel_isResolved) {
|
||||
VirtualMemory::memory_patch(rel_virtual_addr, rel_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_INFO_IF(debug_loader, "function not patched! {}\n",rel_name);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::Relocate(Module* m)
|
||||
{
|
||||
u32 idx = 0;
|
||||
for (auto* rel = m->dynamic_info.relocation_table; reinterpret_cast<u08*>(rel) < reinterpret_cast<u08*>(m->dynamic_info.relocation_table) + m->dynamic_info.relocation_table_size; rel++, idx++)
|
||||
{
|
||||
relocate(idx, rel, m, false);
|
||||
}
|
||||
idx = 0;
|
||||
for (auto* rel = m->dynamic_info.jmp_relocation_table; reinterpret_cast<u08*>(rel) < reinterpret_cast<u08*>(m->dynamic_info.jmp_relocation_table) + m->dynamic_info.jmp_relocation_table_size; rel++, idx++)
|
||||
{
|
||||
relocate(idx, rel, m, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::Resolve(const std::string& name, int Symtype, Module* m, SymbolRecord* return_info) {
|
||||
auto ids = StringUtil::split_string(name, '#');
|
||||
|
||||
if (ids.size() == 3) // symbols are 3 parts name , library , module
|
||||
{
|
||||
const auto* library = FindLibrary(*m, ids.at(1));
|
||||
const auto* module = FindModule(*m, ids.at(2));
|
||||
|
||||
if (library != nullptr && module != nullptr) {
|
||||
SymbolRes sr{};
|
||||
sr.name = ids.at(0);
|
||||
sr.library = library->name;
|
||||
sr.library_version = library->version;
|
||||
sr.module = module->name;
|
||||
sr.module_version_major = module->version_major;
|
||||
sr.module_version_minor = module->version_minor;
|
||||
sr.type = Symtype;
|
||||
|
||||
const SymbolRecord* rec = nullptr;
|
||||
rec = m_hle_symbols.FindSymbol(sr);
|
||||
|
||||
if (rec != nullptr) {
|
||||
*return_info = *rec;
|
||||
} else {
|
||||
auto aeronid = aerolib::find_by_nid(sr.name.c_str());
|
||||
if (aeronid) {
|
||||
return_info->name = aeronid->name;
|
||||
return_info->virtual_address = GetStub(aeronid->nid);
|
||||
} else {
|
||||
return_info->virtual_address = GetStub(sr.name.c_str());
|
||||
return_info->name = "Unknown !!!";
|
||||
}
|
||||
LOG_ERROR_IF(debug_loader, "Linker: Stub resolved {} as {} (lib: {}, mod: {}) \n", sr.name, return_info->name, library->name, module->name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__debugbreak();//den tha prepei na ftasoume edo
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__debugbreak();//oute edo mallon
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using exit_func_t = PS4_SYSV_ABI void (*)();
|
||||
using entry_func_t = PS4_SYSV_ABI void (*)(EntryParams* params, exit_func_t atexit_func);
|
||||
|
||||
static PS4_SYSV_ABI void ProgramExitFunc() {
|
||||
fmt::print("exit function called\n");
|
||||
}
|
||||
|
||||
static void run_main_entry(u64 addr, EntryParams* params, exit_func_t exit_func) {
|
||||
//reinterpret_cast<entry_func_t>(addr)(params, exit_func); // can't be used, stack has to have a specific layout
|
||||
|
||||
asm volatile (
|
||||
"andq $-16, %%rsp\n"// Align to 16 bytes
|
||||
"subq $8, %%rsp\n" // videoout_basic expects the stack to be misaligned
|
||||
|
||||
// Kernel also pushes some more things here during process init
|
||||
// at least: environment, auxv, possibly other things
|
||||
|
||||
"pushq 8(%1)\n" // copy EntryParams to top of stack like the kernel does
|
||||
"pushq 0(%1)\n" // OpenOrbis expects to find it there
|
||||
|
||||
"movq %1, %%rdi\n" // also pass params and exit func
|
||||
"movq %2, %%rsi\n" // as before
|
||||
|
||||
"jmp *%0\n" // can't use call here, as that would mangle the prepared stack.
|
||||
// there's no coming back
|
||||
:
|
||||
: "r"(addr), "r"(params), "r"(exit_func)
|
||||
: "rax", "rsi", "rdi", "rsp", "rbp"
|
||||
);
|
||||
}
|
||||
|
||||
void Linker::Execute()
|
||||
{
|
||||
HLE::Libs::LibKernel::ThreadManagement::Pthread_Init_Self_MainThread();
|
||||
EntryParams p{};
|
||||
p.argc = 1;
|
||||
p.argv[0] = "eboot.bin"; //hmm should be ok?
|
||||
|
||||
const auto& module = m_modules.at(0);
|
||||
run_main_entry(module.elf.GetElfEntry() + module.base_virtual_addr, &p, ProgramExitFunc);
|
||||
}
|
132
src/core/PS4/Linker.h
Normal file
132
src/core/PS4/Linker.h
Normal file
|
@ -0,0 +1,132 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include "Loader/Elf.h"
|
||||
#include "Loader/SymbolsResolver.h"
|
||||
|
||||
struct DynamicModuleInfo;
|
||||
class Linker;
|
||||
|
||||
struct EntryParams {
|
||||
int argc;
|
||||
u32 padding;
|
||||
const char* argv[3];
|
||||
};
|
||||
|
||||
struct ModuleInfo
|
||||
{
|
||||
std::string name;
|
||||
union
|
||||
{
|
||||
u64 value;
|
||||
struct
|
||||
{
|
||||
u32 name_offset;
|
||||
u08 version_minor;
|
||||
u08 version_major;
|
||||
u16 id;
|
||||
};
|
||||
};
|
||||
std::string enc_id;
|
||||
};
|
||||
|
||||
struct LibraryInfo
|
||||
{
|
||||
std::string name;
|
||||
union
|
||||
{
|
||||
u64 value;
|
||||
struct
|
||||
{
|
||||
u32 name_offset;
|
||||
u16 version;
|
||||
u16 id;
|
||||
};
|
||||
};
|
||||
std::string enc_id;
|
||||
};
|
||||
|
||||
struct DynamicModuleInfo
|
||||
{
|
||||
void* hash_table = nullptr;
|
||||
u64 hash_table_size = 0;
|
||||
|
||||
char* str_table = nullptr;
|
||||
u64 str_table_size = 0;
|
||||
|
||||
elf_symbol* symbol_table = nullptr;
|
||||
u64 symbol_table_total_size = 0;
|
||||
u64 symbol_table_entries_size = 0;
|
||||
|
||||
u64 init_virtual_addr = 0;
|
||||
u64 fini_virtual_addr = 0;
|
||||
u64 pltgot_virtual_addr = 0;
|
||||
u64 init_array_virtual_addr = 0;
|
||||
u64 fini_array_virtual_addr = 0;
|
||||
u64 preinit_array_virtual_addr = 0;
|
||||
u64 init_array_size = 0;
|
||||
u64 fini_array_size = 0;
|
||||
u64 preinit_array_size = 0;
|
||||
|
||||
elf_relocation* jmp_relocation_table = nullptr;
|
||||
u64 jmp_relocation_table_size = 0;
|
||||
s64 jmp_relocation_type = 0;
|
||||
|
||||
elf_relocation* relocation_table = nullptr;
|
||||
u64 relocation_table_size = 0;
|
||||
u64 relocation_table_entries_size = 0;
|
||||
|
||||
u64 debug = 0;
|
||||
u64 textrel = 0;
|
||||
u64 flags = 0;
|
||||
|
||||
std::vector<const char*> needed;
|
||||
std::vector<ModuleInfo> import_modules;
|
||||
std::vector<ModuleInfo> export_modules;
|
||||
std::vector<LibraryInfo> import_libs;
|
||||
std::vector<LibraryInfo> export_libs;
|
||||
|
||||
std::string filename; // Filename with absolute path
|
||||
};
|
||||
|
||||
// This struct keeps neccesary info about loaded modules. Main executeable is included too as well
|
||||
struct Module
|
||||
{
|
||||
Elf elf;
|
||||
u64 aligned_base_size = 0;
|
||||
u64 base_virtual_addr = 0; // Base virtual address
|
||||
|
||||
Linker* linker = nullptr;
|
||||
|
||||
std::vector<u08> m_dynamic;
|
||||
std::vector<u08> m_dynamic_data;
|
||||
DynamicModuleInfo dynamic_info{};
|
||||
|
||||
SymbolsResolver export_sym;
|
||||
SymbolsResolver import_sym;
|
||||
};
|
||||
|
||||
class Linker {
|
||||
public:
|
||||
Linker();
|
||||
virtual ~Linker();
|
||||
|
||||
Module* LoadModule(const std::string& elf_name);
|
||||
Module* FindModule(/*u32 id*/);
|
||||
void LoadModuleToMemory(Module* m);
|
||||
void LoadDynamicInfo(Module* m);
|
||||
void LoadSymbols(Module* m);
|
||||
SymbolsResolver& getHLESymbols() { return m_hle_symbols; }
|
||||
void Relocate(Module* m);
|
||||
void Resolve(const std::string& name, int Symtype, Module* m, SymbolRecord* return_info);
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
const ModuleInfo* FindModule(const Module& m, const std::string& id);
|
||||
const LibraryInfo* FindLibrary(const Module& program, const std::string& id);
|
||||
|
||||
std::vector<Module> m_modules;
|
||||
SymbolsResolver m_hle_symbols{};
|
||||
std::mutex m_mutex;
|
||||
};
|
434
src/core/PS4/Loader/Elf.cpp
Normal file
434
src/core/PS4/Loader/Elf.cpp
Normal file
|
@ -0,0 +1,434 @@
|
|||
#include "Elf.h"
|
||||
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
constexpr bool log_file_loader = true; // disable it to disable logging
|
||||
|
||||
static std::string_view getProgramTypeName(program_type_es type) {
|
||||
switch (type) {
|
||||
case PT_FAKE: return "PT_FAKE";
|
||||
case PT_NPDRM_EXEC: return "PT_NPDRM_EXEC";
|
||||
case PT_NPDRM_DYNLIB: return "PT_NPDRM_DYNLIB";
|
||||
case PT_SYSTEM_EXEC: return "PT_SYSTEM_EXEC";
|
||||
case PT_SYSTEM_DYNLIB: return "PT_SYSTEM_DYNLIB";
|
||||
case PT_HOST_KERNEL: return "PT_HOST_KERNEL";
|
||||
case PT_SECURE_MODULE: return "PT_SECURE_MODULE";
|
||||
case PT_SECURE_KERNEL: return "PT_SECURE_KERNEL";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getIdentClassName(ident_class_es elf_class) {
|
||||
switch (elf_class) {
|
||||
case ELF_CLASS_NONE: return "ELF_CLASS_NONE";
|
||||
case ELF_CLASS_32: return "ELF_CLASS_32";
|
||||
case ELF_CLASS_64: return "ELF_CLASS_64";
|
||||
case ELF_CLASS_NUM: return "ELF_CLASS_NUM";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getIdentEndianName(ident_endian_es endian) {
|
||||
switch (endian) {
|
||||
case ELF_DATA_NONE: return "ELF_DATA_NONE";
|
||||
case ELF_DATA_2LSB: return "ELF_DATA_2LSB";
|
||||
case ELF_DATA_2MSB: return "ELF_DATA_2MSB";
|
||||
case ELF_DATA_NUM: return "ELF_DATA_NUM";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getIdentVersionName(ident_version_es version) {
|
||||
switch (version) {
|
||||
case ELF_VERSION_NONE: return "ELF_VERSION_NONE";
|
||||
case ELF_VERSION_CURRENT: return "ELF_VERSION_CURRENT";
|
||||
case ELF_VERSION_NUM: return "ELF_VERSION_NUM";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getIdentOsabiName(ident_osabi_es osabi) {
|
||||
switch (osabi) {
|
||||
case ELF_OSABI_NONE: return "ELF_OSABI_NONE";
|
||||
case ELF_OSABI_HPUX: return "ELF_OSABI_HPUX";
|
||||
case ELF_OSABI_NETBSD: return "ELF_OSABI_NETBSD";
|
||||
case ELF_OSABI_LINUX: return "ELF_OSABI_LINUX";
|
||||
case ELF_OSABI_SOLARIS: return "ELF_OSABI_SOLARIS";
|
||||
case ELF_OSABI_AIX: return "ELF_OSABI_AIX";
|
||||
case ELF_OSABI_IRIX: return "ELF_OSABI_IRIX";
|
||||
case ELF_OSABI_FREEBSD: return "ELF_OSABI_FREEBSD";
|
||||
case ELF_OSABI_TRU64: return "ELF_OSABI_TRU64";
|
||||
case ELF_OSABI_MODESTO: return "ELF_OSABI_MODESTO";
|
||||
case ELF_OSABI_OPENBSD: return "ELF_OSABI_OPENBSD";
|
||||
case ELF_OSABI_OPENVMS: return "ELF_OSABI_OPENVMS";
|
||||
case ELF_OSABI_NSK: return "ELF_OSABI_NSK";
|
||||
case ELF_OSABI_AROS: return "ELF_OSABI_AROS";
|
||||
case ELF_OSABI_ARM_AEABI: return "ELF_OSABI_ARM_AEABI";
|
||||
case ELF_OSABI_ARM: return "ELF_OSABI_ARM";
|
||||
case ELF_OSABI_STANDALONE: return "ELF_OSABI_STANDALONE";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getIdentAbiversionName(ident_abiversion_es version) {
|
||||
switch (version) {
|
||||
case ELF_ABI_VERSION_AMDGPU_HSA_V2: return "ELF_ABI_VERSION_AMDGPU_HSA_V2";
|
||||
case ELF_ABI_VERSION_AMDGPU_HSA_V3: return "ELF_ABI_VERSION_AMDGPU_HSA_V3";
|
||||
case ELF_ABI_VERSION_AMDGPU_HSA_V4: return "ELF_ABI_VERSION_AMDGPU_HSA_V4";
|
||||
case ELF_ABI_VERSION_AMDGPU_HSA_V5: return "ELF_ABI_VERSION_AMDGPU_HSA_V5";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
static std::string_view getVersion(e_version_es version) {
|
||||
switch (version) {
|
||||
case EV_NONE: return "EV_NONE";
|
||||
case EV_CURRENT: return "EV_CURRENT";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getType(e_type_s type) {
|
||||
switch (type) {
|
||||
case ET_NONE: return "ET_NONE";
|
||||
case ET_REL: return "ET_REL";
|
||||
case ET_EXEC: return "ET_EXEC";
|
||||
case ET_DYN: return "ET_DYN";
|
||||
case ET_CORE: return "ET_CORE";
|
||||
case ET_SCE_EXEC: return "ET_SCE_EXEC";
|
||||
case ET_SCE_STUBLIB: return "ET_SCE_STUBLIB";
|
||||
case ET_SCE_DYNEXEC: return "ET_SCE_DYNEXEC";
|
||||
case ET_SCE_DYNAMIC: return "ET_SCE_DYNAMIC";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view getMachine(e_machine_es machine) {
|
||||
switch (machine) {
|
||||
case EM_X86_64: return "EM_X86_64";
|
||||
default: return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
Elf::~Elf() { Reset(); }
|
||||
|
||||
void Elf::Reset() { m_f.close(); }
|
||||
|
||||
void Elf::Open(const std::string& file_name) {
|
||||
Reset();
|
||||
|
||||
m_f.open(file_name, Common::FS::OpenMode::Read);
|
||||
m_f.read(m_self);
|
||||
|
||||
if (is_self = isSelfFile(); !is_self) {
|
||||
m_f.seek(0, Common::FS::SeekMode::Set);
|
||||
} else {
|
||||
m_self_segments.resize(m_self.segment_count);
|
||||
m_f.read(m_self_segments);
|
||||
}
|
||||
|
||||
const u64 elf_header_pos = m_f.tell();
|
||||
m_f.read(m_elf_header);
|
||||
if (!isElfFile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto load_headers = [this]<typename T>(std::vector<T>& out, u64 offset, u16 num) {
|
||||
if (!num) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.resize(num);
|
||||
m_f.seek(offset, Common::FS::SeekMode::Set);
|
||||
m_f.read(out);
|
||||
};
|
||||
|
||||
load_headers(m_elf_phdr, elf_header_pos + m_elf_header.e_phoff, m_elf_header.e_phnum);
|
||||
load_headers(m_elf_shdr, elf_header_pos + m_elf_header.e_shoff, m_elf_header.e_shnum);
|
||||
|
||||
if (is_self) {
|
||||
u64 header_size = 0;
|
||||
header_size += sizeof(self_header);
|
||||
header_size += sizeof(self_segment_header) * m_self.segment_count;
|
||||
header_size += sizeof(elf_header);
|
||||
header_size += m_elf_header.e_phnum * m_elf_header.e_phentsize;
|
||||
header_size += m_elf_header.e_shnum * m_elf_header.e_shentsize;
|
||||
header_size += 15;
|
||||
header_size &= ~15; // Align
|
||||
|
||||
if (m_elf_header.e_ehsize - header_size >= sizeof(elf_program_id_header)) {
|
||||
m_f.seek(header_size, Common::FS::SeekMode::Set);
|
||||
m_f.read(m_self_id_header);
|
||||
}
|
||||
}
|
||||
|
||||
DebugDump();
|
||||
}
|
||||
|
||||
bool Elf::isSelfFile() const {
|
||||
if (m_self.magic != self_header::signature) [[unlikely]] {
|
||||
LOG_ERROR_IF(log_file_loader, "Not a SELF file.Magic file mismatched !current = {:#x} required = {:#x}\n ", m_self.magic,
|
||||
self_header::signature);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_self.version != 0x00 || m_self.mode != 0x01 || m_self.endian != 0x01 || m_self.attributes != 0x12) [[unlikely]] {
|
||||
LOG_ERROR_IF(log_file_loader, "Unknown SELF file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_self.category != 0x01 || m_self.program_type != 0x01) [[unlikely]] {
|
||||
LOG_ERROR_IF(log_file_loader, "Unknown SELF file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Elf::isElfFile() const {
|
||||
if (m_elf_header.e_ident.magic[EI_MAG0] != ELFMAG0 || m_elf_header.e_ident.magic[EI_MAG1] != ELFMAG1 ||
|
||||
m_elf_header.e_ident.magic[EI_MAG2] != ELFMAG2 || m_elf_header.e_ident.magic[EI_MAG3] != ELFMAG3) {
|
||||
LOG_ERROR_IF(log_file_loader, "Not an ELF file magic is wrong!\n");
|
||||
return false;
|
||||
}
|
||||
if (m_elf_header.e_ident.ei_class != ELF_CLASS_64) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_ident[EI_CLASS] expected 0x02 is ({:#x})\n", static_cast<u32>(m_elf_header.e_ident.ei_class));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_ident.ei_data != ELF_DATA_2LSB) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_ident[EI_DATA] expected 0x01 is ({:#x})\n", static_cast<u32>(m_elf_header.e_ident.ei_data));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_ident.ei_version != ELF_VERSION_CURRENT) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_ident[EI_VERSION] expected 0x01 is ({:#x})\n", static_cast<u32>(m_elf_header.e_ident.ei_version));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_ident.ei_osabi != ELF_OSABI_FREEBSD) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_ident[EI_OSABI] expected 0x09 is ({:#x})\n", static_cast<u32>(m_elf_header.e_ident.ei_osabi));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_ident.ei_abiversion != ELF_ABI_VERSION_AMDGPU_HSA_V2) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_ident[EI_ABIVERSION] expected 0x00 is ({:#x})\n", static_cast<u32>(m_elf_header.e_ident.ei_abiversion));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_type != ET_SCE_DYNEXEC && m_elf_header.e_type != ET_SCE_DYNAMIC && m_elf_header.e_type != ET_SCE_EXEC) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_type expected 0xFE10 OR 0xFE18 OR 0xfe00 is ({:#x})\n", static_cast<u32>(m_elf_header.e_type));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_machine != EM_X86_64) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_machine expected 0x3E is ({:#x})\n", static_cast<u32>(m_elf_header.e_machine));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_version != EV_CURRENT) {
|
||||
LOG_ERROR_IF(log_file_loader, "m_elf_header.e_version expected 0x01 is ({:#x})\n", static_cast<u32>(m_elf_header.e_version));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_phentsize != sizeof(elf_program_header)) {
|
||||
LOG_ERROR_IF(log_file_loader, "e_phentsize ({}) != sizeof(elf_program_header)\n", static_cast<u32>(m_elf_header.e_phentsize));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_elf_header.e_shentsize > 0 &&
|
||||
m_elf_header.e_shentsize != sizeof(elf_section_header)) // Commercial games doesn't appear to have section headers
|
||||
{
|
||||
LOG_ERROR_IF(log_file_loader, "e_shentsize ({}) != sizeof(elf_section_header)\n", m_elf_header.e_shentsize);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Elf::DebugDump() {
|
||||
if (is_self) { // If we load elf instead
|
||||
LOG_INFO_IF(log_file_loader, (SElfHeaderStr()));
|
||||
for (u16 i = 0; i < m_self.segment_count; i++) {
|
||||
LOG_INFO_IF(log_file_loader, SELFSegHeader(i));
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO_IF(log_file_loader, ElfHeaderStr());
|
||||
|
||||
if (m_elf_header.e_phentsize > 0) {
|
||||
LOG_INFO_IF(log_file_loader, "Program headers:\n");
|
||||
for (u16 i = 0; i < m_elf_header.e_phnum; i++) {
|
||||
LOG_INFO_IF(log_file_loader, ElfPHeaderStr(i));
|
||||
}
|
||||
}
|
||||
if (m_elf_header.e_shentsize > 0) {
|
||||
LOG_INFO_IF(log_file_loader, "Section headers:\n");
|
||||
for (u16 i = 0; i < m_elf_header.e_shnum; i++) {
|
||||
LOG_INFO_IF(log_file_loader, "--- shdr {} --\n", i);
|
||||
LOG_INFO_IF(log_file_loader, "sh_name ........: {}\n", m_elf_shdr[i].sh_name);
|
||||
LOG_INFO_IF(log_file_loader, "sh_type ........: {:#010x}\n", m_elf_shdr[i].sh_type);
|
||||
LOG_INFO_IF(log_file_loader, "sh_flags .......: {:#018x}\n", m_elf_shdr[i].sh_flags);
|
||||
LOG_INFO_IF(log_file_loader, "sh_addr ........: {:#018x}\n", m_elf_shdr[i].sh_addr);
|
||||
LOG_INFO_IF(log_file_loader, "sh_offset ......: {:#018x}\n", m_elf_shdr[i].sh_offset);
|
||||
LOG_INFO_IF(log_file_loader, "sh_size ........: {:#018x}\n", m_elf_shdr[i].sh_size);
|
||||
LOG_INFO_IF(log_file_loader, "sh_link ........: {:#010x}\n", m_elf_shdr[i].sh_link);
|
||||
LOG_INFO_IF(log_file_loader, "sh_info ........: {:#010x}\n", m_elf_shdr[i].sh_info);
|
||||
LOG_INFO_IF(log_file_loader, "sh_addralign ...: {:#018x}\n", m_elf_shdr[i].sh_addralign);
|
||||
LOG_INFO_IF(log_file_loader, "sh_entsize .....: {:#018x}\n", m_elf_shdr[i].sh_entsize);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_self) {
|
||||
LOG_INFO_IF(log_file_loader, "SELF info:\n");
|
||||
LOG_INFO_IF(log_file_loader, "auth id ............: {:#018x}\n", m_self_id_header.authid);
|
||||
LOG_INFO_IF(log_file_loader, "program type .......: {}\n", getProgramTypeName(m_self_id_header.program_type));
|
||||
LOG_INFO_IF(log_file_loader, "app version ........: {:#018x}\n", m_self_id_header.appver);
|
||||
LOG_INFO_IF(log_file_loader, "fw version .........: {:#018x}\n", m_self_id_header.firmver);
|
||||
std::string digest;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
digest += fmt::format("{:02X}", m_self_id_header.digest[i]);
|
||||
}
|
||||
LOG_INFO_IF(log_file_loader, "digest..............: 0x{}\n", digest);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Elf::SElfHeaderStr() {
|
||||
std::string header = fmt::format("======= SELF HEADER =========\n", m_self.magic);
|
||||
header += fmt::format("magic ..............: 0x{:X}\n", m_self.magic);
|
||||
header += fmt::format("version ............: {}\n", m_self.version);
|
||||
header += fmt::format("mode ...............: {:#04x}\n", m_self.mode);
|
||||
header += fmt::format("endian .............: {}\n", m_self.endian);
|
||||
header += fmt::format("attributes .........: {:#04x}\n", m_self.attributes);
|
||||
header += fmt::format("category ...........: {:#04x}\n", m_self.category);
|
||||
header += fmt::format("program_type........: {:#04x}\n", m_self.program_type);
|
||||
header += fmt::format("padding1 ...........: {:#06x}\n", m_self.padding1);
|
||||
header += fmt::format("header size ........: {}\n", m_self.header_size);
|
||||
header += fmt::format("meta size ..........: {}\n", m_self.meta_size);
|
||||
header += fmt::format("file size ..........: {}\n", m_self.file_size);
|
||||
header += fmt::format("padding2 ...........: {:#010x}\n", m_self.padding2);
|
||||
header += fmt::format("segment count ......: {}\n", m_self.segment_count);
|
||||
header += fmt::format("unknown 1A .........: {:#06x}\n", m_self.unknown1A);
|
||||
header += fmt::format("padding3 ...........: {:#010x}\n", m_self.padding3);
|
||||
return header;
|
||||
}
|
||||
|
||||
std::string Elf::SELFSegHeader(u16 no) {
|
||||
const auto segment_header = m_self_segments[no];
|
||||
std::string header = fmt::format("====== SEGMENT HEADER {} ========\n", no);
|
||||
header += fmt::format("flags ............: {:#018x}\n", segment_header.flags);
|
||||
header += fmt::format("file offset ......: {:#018x}\n", segment_header.file_offset);
|
||||
header += fmt::format("file size ........: {}\n", segment_header.file_size);
|
||||
header += fmt::format("memory size ......: {}\n", segment_header.memory_size);
|
||||
return header;
|
||||
}
|
||||
|
||||
std::string Elf::ElfHeaderStr() {
|
||||
std::string header = fmt::format("======= Elf header ===========\n");
|
||||
header += fmt::format("ident ............: 0x");
|
||||
for (auto i : m_elf_header.e_ident.magic) {
|
||||
header += fmt::format("{:02X}", i);
|
||||
}
|
||||
header += fmt::format("\n");
|
||||
|
||||
header += fmt::format("ident class.......: {}\n", getIdentClassName(m_elf_header.e_ident.ei_class));
|
||||
header += fmt::format("ident data .......: {}\n", getIdentEndianName(m_elf_header.e_ident.ei_data));
|
||||
header += fmt::format("ident version.....: {}\n", getIdentVersionName(m_elf_header.e_ident.ei_version));
|
||||
header += fmt::format("ident osabi .....: {}\n", getIdentOsabiName(m_elf_header.e_ident.ei_osabi));
|
||||
header += fmt::format("ident abiversion..: {}\n", getIdentAbiversionName(m_elf_header.e_ident.ei_abiversion));
|
||||
|
||||
header += fmt::format("ident UNK ........: 0x");
|
||||
for (auto i : m_elf_header.e_ident.pad) {
|
||||
header += fmt::format("{:02X}", i);
|
||||
}
|
||||
header += fmt::format("\n");
|
||||
|
||||
header += fmt::format("type ............: {}\n", getType(m_elf_header.e_type));
|
||||
header += fmt::format("machine ..........: {}\n", getMachine(m_elf_header.e_machine));
|
||||
header += fmt::format("version ..........: {}\n", getVersion(m_elf_header.e_version));
|
||||
header += fmt::format("entry ............: {:#018x}\n", m_elf_header.e_entry);
|
||||
header += fmt::format("phoff ............: {:#018x}\n", m_elf_header.e_phoff);
|
||||
header += fmt::format("shoff ............: {:#018x}\n", m_elf_header.e_shoff);
|
||||
header += fmt::format("flags ............: {:#010x}\n", m_elf_header.e_flags);
|
||||
header += fmt::format("ehsize ...........: {}\n", m_elf_header.e_ehsize);
|
||||
header += fmt::format("phentsize ........: {}\n", m_elf_header.e_phentsize);
|
||||
header += fmt::format("phnum ............: {}\n", m_elf_header.e_phnum);
|
||||
header += fmt::format("shentsize ........: {}\n", m_elf_header.e_shentsize);
|
||||
header += fmt::format("shnum ............: {}\n", m_elf_header.e_shnum);
|
||||
header += fmt::format("shstrndx .........: {}\n", m_elf_header.e_shstrndx);
|
||||
return header;
|
||||
}
|
||||
|
||||
std::string Elf::ElfPheaderTypeStr(u32 type) {
|
||||
switch (type) {
|
||||
case PT_NULL: return "Null";
|
||||
case PT_LOAD: return "Loadable";
|
||||
case PT_DYNAMIC: return "Dynamic";
|
||||
case PT_INTERP: return "Interpreter Path";
|
||||
case PT_NOTE: return "Note";
|
||||
case PT_SHLIB: return "Section Header Library";
|
||||
case PT_PHDR: return "Program Header";
|
||||
case PT_TLS: return "Thread-Local Storage";
|
||||
case PT_NUM: return "Defined Sections Number";
|
||||
case PT_SCE_RELA: return "SCE Relative";
|
||||
case PT_SCE_DYNLIBDATA: return "SCE Dynamic Library Data";
|
||||
case PT_SCE_PROCPARAM: return "SCE Processor Parameters";
|
||||
case PT_SCE_MODULE_PARAM: return "SCE Module Parameters";
|
||||
case PT_SCE_RELRO: return "SCE Read-Only After Relocation";
|
||||
case PT_GNU_EH_FRAME: return "GNU Entry Header Frame";
|
||||
case PT_GNU_STACK: return "GNU Stack (executability)";
|
||||
case PT_GNU_RELRO: return "GNU Read-Only After Relocation";
|
||||
case PT_SCE_COMMENT: return "SCE Comment";
|
||||
case PT_SCE_LIBVERSION: return "SCE Library Version";
|
||||
default: return "Unknown Section";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Elf::ElfPheaderFlagsStr(u32 flags) {
|
||||
std::string flg = "(";
|
||||
flg += (flags & PF_READ) ? "R" : "_";
|
||||
flg += (flags & PF_WRITE) ? "W" : "_";
|
||||
flg += (flags & PF_EXEC) ? "X" : "_";
|
||||
flg += ")";
|
||||
return flg;
|
||||
}
|
||||
|
||||
std::string Elf::ElfPHeaderStr(u16 no) {
|
||||
std::string header = fmt::format("====== PROGRAM HEADER {} ========\n", no);
|
||||
header += fmt::format("p_type ....: {}\n", ElfPheaderTypeStr(m_elf_phdr[no].p_type));
|
||||
header += fmt::format("p_flags ...: {:#010x}\n", static_cast<u32>(m_elf_phdr[no].p_flags));
|
||||
header += fmt::format("p_offset ..: {:#018x}\n", m_elf_phdr[no].p_offset);
|
||||
header += fmt::format("p_vaddr ...: {:#018x}\n", m_elf_phdr[no].p_vaddr);
|
||||
header += fmt::format("p_paddr ...: {:#018x}\n", m_elf_phdr[no].p_paddr);
|
||||
header += fmt::format("p_filesz ..: {:#018x}\n", m_elf_phdr[no].p_filesz);
|
||||
header += fmt::format("p_memsz ...: {:#018x}\n", m_elf_phdr[no].p_memsz);
|
||||
header += fmt::format("p_align ...: {:#018x}\n", m_elf_phdr[no].p_align);
|
||||
return header;
|
||||
}
|
||||
|
||||
void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
|
||||
if (!is_self) {
|
||||
// It's elf file
|
||||
m_f.seek(file_offset, Common::FS::SeekMode::Set);
|
||||
m_f.read(reinterpret_cast<void*>(static_cast<uintptr_t>(virtual_addr)), size);
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < m_self.segment_count; i++) {
|
||||
const auto& seg = m_self_segments[i];
|
||||
|
||||
if (seg.IsBlocked()) {
|
||||
auto phdr_id = seg.GetId();
|
||||
const auto& phdr = m_elf_phdr[phdr_id];
|
||||
|
||||
if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
|
||||
auto offset = file_offset - phdr.p_offset;
|
||||
m_f.seek(offset + seg.file_offset, Common::FS::SeekMode::Set);
|
||||
m_f.read(reinterpret_cast<void*>(static_cast<uintptr_t>(virtual_addr)), size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
BREAKPOINT(); // Hmm we didn't return something...
|
||||
}
|
504
src/core/PS4/Loader/Elf.h
Normal file
504
src/core/PS4/Loader/Elf.h
Normal file
|
@ -0,0 +1,504 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cinttypes>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../types.h"
|
||||
#include "../../FsFile.h"
|
||||
|
||||
struct self_header
|
||||
{
|
||||
static const u32 signature = 0x1D3D154Fu;
|
||||
|
||||
u32 magic;
|
||||
u08 version;
|
||||
u08 mode;
|
||||
u08 endian;// 1 is little endian
|
||||
u08 attributes;
|
||||
u08 category;
|
||||
u08 program_type;
|
||||
u16 padding1;
|
||||
u16 header_size;
|
||||
u16 meta_size;
|
||||
u32 file_size;
|
||||
u32 padding2;
|
||||
u16 segment_count;
|
||||
u16 unknown1A; //always 0x22
|
||||
u32 padding3;
|
||||
};
|
||||
|
||||
struct self_segment_header
|
||||
{
|
||||
bool IsBlocked() const {
|
||||
return (flags & 0x800) != 0;//0 or 0x800
|
||||
}
|
||||
|
||||
u32 GetId() const {
|
||||
return (flags >> 20u) & 0xFFFu;
|
||||
}
|
||||
|
||||
bool IsOrdered() const {
|
||||
return (flags & 1) != 0;//0 or 1
|
||||
}
|
||||
|
||||
bool IsEncrypted() const {
|
||||
return (flags & 2) != 0;//0 or 2
|
||||
}
|
||||
|
||||
bool IsSigned() const {
|
||||
return (flags & 4) != 0;//0 or 4
|
||||
}
|
||||
|
||||
bool IsCompressed() const {
|
||||
return (flags & 8) != 0;//0 or 8
|
||||
}
|
||||
|
||||
u64 flags;
|
||||
u64 file_offset;
|
||||
u64 file_size;
|
||||
u64 memory_size;
|
||||
};
|
||||
|
||||
|
||||
constexpr u08 EI_MAG0 = 0;/* e_ident[] indexes */
|
||||
constexpr u08 EI_MAG1 = 1;
|
||||
constexpr u08 EI_MAG2 = 2;
|
||||
constexpr u08 EI_MAG3 = 3;
|
||||
constexpr u08 EI_CLASS = 4;
|
||||
constexpr u08 EI_DATA = 5;
|
||||
constexpr u08 EI_VERSION = 6;
|
||||
constexpr u08 EI_OSABI = 7;
|
||||
constexpr u08 EI_ABIVERSION = 8;
|
||||
|
||||
// Magic number
|
||||
constexpr u08 ELFMAG0 = 0x7F;
|
||||
constexpr u08 ELFMAG1 = 'E';
|
||||
constexpr u08 ELFMAG2 = 'L';
|
||||
constexpr u08 ELFMAG3 = 'F';
|
||||
|
||||
typedef enum : u16 {
|
||||
ET_NONE = 0x0,
|
||||
ET_REL = 0x1,
|
||||
ET_EXEC = 0x2,
|
||||
ET_DYN = 0x3,
|
||||
ET_CORE = 0x4,
|
||||
ET_SCE_EXEC = 0xfe00,
|
||||
ET_SCE_STUBLIB = 0xfe0c,
|
||||
ET_SCE_DYNEXEC = 0xfe10,
|
||||
ET_SCE_DYNAMIC = 0xfe18
|
||||
} e_type_s;
|
||||
|
||||
typedef enum : u16 {
|
||||
EM_NONE = 0, /* No machine */
|
||||
EM_M32 = 1, /* AT&T WE 32100 */
|
||||
EM_SPARC = 2, /* SPARC */
|
||||
EM_386 = 3, /* Intel 80386 */
|
||||
EM_68K = 4, /* Motorola 68000 */
|
||||
EM_88K = 5, /* Motorola 88000 */
|
||||
EM_860 = 7, /* Intel 80860 */
|
||||
EM_MIPS = 8, /* MIPS I Architecture */
|
||||
EM_S370 = 9, /* IBM System/370 Processor */
|
||||
EM_MIPS_RS3_LE = 10, /* MIPS RS3000 Little-endian */
|
||||
EM_PARISC = 15, /* Hewlett-Packard PA-RISC */
|
||||
EM_VPP500 = 17, /* Fujitsu VPP500 */
|
||||
EM_SPARC32PLUS = 18, /* Enhanced instruction set SPARC */
|
||||
EM_960 = 19, /* Intel 80960 */
|
||||
EM_PPC = 20, /* PowerPC */
|
||||
EM_PPC64 = 21, /* 64-bit PowerPC */
|
||||
EM_S390 = 22, /* IBM System/390 Processor */
|
||||
EM_V800 = 36, /* NEC V800 */
|
||||
EM_FR20 = 37, /* Fujitsu FR20 */
|
||||
EM_RH32 = 38, /* TRW RH-32 */
|
||||
EM_RCE = 39, /* Motorola RCE */
|
||||
EM_ARM = 40, /* Advanced RISC Machines ARM */
|
||||
EM_ALPHA = 41, /* Digital Alpha */
|
||||
EM_SH = 42, /* Hitachi SH */
|
||||
EM_SPARCV9 = 43, /* SPARC Version 9 */
|
||||
EM_TRICORE = 44, /* Siemens TriCore embedded processor */
|
||||
EM_ARC = 45, /* Argonaut RISC Core, Argonaut Technologies Inc. */
|
||||
EM_H8_300 = 46, /* Hitachi H8/300 */
|
||||
EM_H8_300H = 47, /* Hitachi H8/300H */
|
||||
EM_H8S = 48, /* Hitachi H8S */
|
||||
EM_H8_500 = 49, /* Hitachi H8/500 */
|
||||
EM_IA_64 = 50, /* Intel IA-64 processor architecture */
|
||||
EM_MIPS_X = 51, /* Stanford MIPS-X */
|
||||
EM_COLDFIRE = 52, /* Motorola ColdFire */
|
||||
EM_68HC12 = 53, /* Motorola M68HC12 */
|
||||
EM_MMA = 54, /* Fujitsu MMA Multimedia Accelerator */
|
||||
EM_PCP = 55, /* Siemens PCP */
|
||||
EM_NCPU = 56, /* Sony nCPU embedded RISC processor */
|
||||
EM_NDR1 = 57, /* Denso NDR1 microprocessor */
|
||||
EM_STARCORE = 58, /* Motorola Star*Core processor */
|
||||
EM_ME16 = 59, /* Toyota ME16 processor */
|
||||
EM_ST100 = 60, /* STMicroelectronics ST100 processor */
|
||||
EM_TINYJ = 61, /* Advanced Logic Corp. TinyJ embedded processor family */
|
||||
EM_X86_64 = 62, /* AMD x86-64 architecture (PS4) */
|
||||
EM_PDSP = 63, /* Sony DSP Processor */
|
||||
EM_PDP10 = 64, /* Digital Equipment Corp. PDP-10 */
|
||||
EM_PDP11 = 65, /* Digital Equipment Corp. PDP-11 */
|
||||
EM_FX66 = 66, /* Siemens FX66 microcontroller */
|
||||
EM_ST9PLUS = 67, /* STMicroelectronics ST9+ 8/16 bit microcontroller */
|
||||
EM_ST7 = 68, /* STMicroelectronics ST7 8-bit microcontroller */
|
||||
EM_68HC16 = 69, /* Motorola MC68HC16 Microcontroller */
|
||||
EM_68HC11 = 70, /* Motorola MC68HC11 Microcontroller */
|
||||
EM_68HC08 = 71, /* Motorola MC68HC08 Microcontroller */
|
||||
EM_68HC05 = 72, /* Motorola MC68HC05 Microcontroller */
|
||||
EM_SVX = 73, /* Silicon Graphics SVx */
|
||||
EM_ST19 = 75, /* Digital VAX */
|
||||
EM_CRIS = 76, /* Axis Communications 32-bit embedded processor */
|
||||
EM_JAVELIN = 77, /* Infineon Technologies 32-bit embedded processor */
|
||||
EM_FIREPATH = 78, /* Element 14 64-bit DSP Processor */
|
||||
EM_ZSP = 79, /* LSI Logic 16-bit DSP Processor */
|
||||
EM_MMIX = 80, /* Donald Knuth's educational 64-bit processor */
|
||||
EM_HUANY = 81, /* Harvard University machine-independent object files */
|
||||
EM_PRISM = 82, /* SiTera Prism */
|
||||
EM_AVR = 83, /* Atmel AVR 8-bit microcontroller */
|
||||
EM_FR30 = 84, /* Fujitsu FR30 */
|
||||
EM_D10V = 85, /* Mitsubishi D10V */
|
||||
EM_D30V = 86, /* Mitsubishi D30V */
|
||||
EM_V850 = 87, /* NEC v850 */
|
||||
EM_M32R = 88, /* Mitsubishi M32R */
|
||||
EM_MN10300 = 89, /* Matsushita MN10300 */
|
||||
EM_MN10200 = 90, /* Matsushita MN10200 */
|
||||
EM_PJ = 91, /* PicoJava */
|
||||
EM_OPENRISC = 92, /* OpenRISC 32-bit embedded processor */
|
||||
EM_ARC_A5 = 93, /* ARC Cores Tangent-A5 */
|
||||
EM_XTENSA = 94, /* Tensilica Xtensa Architecture */
|
||||
EM_VIDEOCORE = 95, /* Alphamosaic VideoCore processor */
|
||||
EM_TMM_GPP = 96, /* Thompson Multimedia General Purpose Processor */
|
||||
EM_NS32K = 97, /* National Semiconductor 32000 series */
|
||||
EM_TPC = 98, /* Tenor Network TPC processor */
|
||||
EM_SNP1K = 99, /* Trebia SNP 1000 processor */
|
||||
EM_ST200 = 100, /* STMicroelectronics (www.st.com) ST200 microcontroller */
|
||||
EM_IP2K = 101, /* Ubicom IP2xxx microcontroller family */
|
||||
EM_MAX = 102, /* MAX Processor */
|
||||
EM_CR = 103, /* National Semiconductor CompactRISC microprocessor */
|
||||
EM_F2MC16 = 104, /* Fujitsu F2MC16 */
|
||||
EM_MSP430 = 105, /* Texas Instruments embedded microcontroller msp430 */
|
||||
EM_BLACKFIN = 106, /* Analog Devices Blackfin (DSP) processor */
|
||||
EM_SE_C33 = 107, /* S1C33 Family of Seiko Epson processors */
|
||||
EM_SEP = 108, /* Sharp embedded microprocessor */
|
||||
EM_ARCA = 109, /* Arca RISC Microprocessor */
|
||||
EM_UNICORE = 110 /* Microprocessor series from PKU-Unity Ltd. and MPRC */
|
||||
} e_machine_es;
|
||||
|
||||
typedef enum :u32 {
|
||||
EV_NONE = 0x0,
|
||||
EV_CURRENT = 0x1
|
||||
} e_version_es;
|
||||
|
||||
typedef enum : u08 {
|
||||
ELF_CLASS_NONE =0x0,
|
||||
ELF_CLASS_32 =0x1,
|
||||
ELF_CLASS_64 =0x2,
|
||||
ELF_CLASS_NUM =0x3
|
||||
} ident_class_es;
|
||||
|
||||
typedef enum : u08 {
|
||||
ELF_DATA_NONE = 0x0,
|
||||
ELF_DATA_2LSB = 0x1,
|
||||
ELF_DATA_2MSB = 0x2,
|
||||
ELF_DATA_NUM = 0x3
|
||||
} ident_endian_es;
|
||||
|
||||
typedef enum :u08 {
|
||||
ELF_VERSION_NONE = 0x0,
|
||||
ELF_VERSION_CURRENT = 0x1,
|
||||
ELF_VERSION_NUM = 0x2
|
||||
} ident_version_es;
|
||||
|
||||
typedef enum :u08 {
|
||||
ELF_OSABI_NONE = 0x0, /* No extensions or unspecified */
|
||||
ELF_OSABI_HPUX = 0x1, /* Hewlett-Packard HP-UX */
|
||||
ELF_OSABI_NETBSD = 0x2, /* NetBSD */
|
||||
ELF_OSABI_LINUX = 0x3, /* Linux */
|
||||
ELF_OSABI_SOLARIS = 0x6, /* Sun Solaris */
|
||||
ELF_OSABI_AIX = 0x7, /* AIX */
|
||||
ELF_OSABI_IRIX = 0x8, /* IRIX */
|
||||
ELF_OSABI_FREEBSD = 0x9, /* FreeBSD (PS4) */
|
||||
ELF_OSABI_TRU64 = 0xA, /* Compaq TRU64 UNIX */
|
||||
ELF_OSABI_MODESTO = 0xB, /* Novell Modesto */
|
||||
ELF_OSABI_OPENBSD = 0xC, /* Open BSD */
|
||||
ELF_OSABI_OPENVMS = 0xD, /* Open VMS */
|
||||
ELF_OSABI_NSK = 0xE, /* Hewlett-Packard Non-Stop Kernel */
|
||||
ELF_OSABI_AROS = 0xF, /* Amiga Research OS */
|
||||
ELF_OSABI_ARM_AEABI = 0x40, /* ARM EABI */
|
||||
ELF_OSABI_ARM = 0x61, /* ARM */
|
||||
ELF_OSABI_STANDALONE = 0xFF /* Standalone (embedded applications) */
|
||||
} ident_osabi_es;
|
||||
|
||||
typedef enum :u08 {
|
||||
ELF_ABI_VERSION_AMDGPU_HSA_V2=0x0,
|
||||
ELF_ABI_VERSION_AMDGPU_HSA_V3=0x1,
|
||||
ELF_ABI_VERSION_AMDGPU_HSA_V4=0x2,
|
||||
ELF_ABI_VERSION_AMDGPU_HSA_V5=0x3
|
||||
} ident_abiversion_es;
|
||||
|
||||
struct elf_ident {
|
||||
u08 magic[4];
|
||||
ident_class_es ei_class;
|
||||
ident_endian_es ei_data;
|
||||
ident_version_es ei_version;
|
||||
ident_osabi_es ei_osabi;
|
||||
ident_abiversion_es ei_abiversion;
|
||||
u08 pad[6];
|
||||
};
|
||||
|
||||
struct elf_header
|
||||
{
|
||||
static const u32 signature = 0x7F454C46u;
|
||||
|
||||
elf_ident e_ident; /* ELF identification */
|
||||
e_type_s e_type; /* Object file type */
|
||||
e_machine_es e_machine; /* Machine type */
|
||||
e_version_es e_version; /* Object file version */
|
||||
u64 e_entry; /* Entry point address */
|
||||
u64 e_phoff; /* Program header offset */
|
||||
u64 e_shoff; /* Section header offset */
|
||||
u32 e_flags; /* Processor-specific flags */
|
||||
u16 e_ehsize; /* ELF header size */
|
||||
u16 e_phentsize; /* Size of program header entry */
|
||||
u16 e_phnum; /* Number of program header entries */
|
||||
u16 e_shentsize; /* Size of section header entry */
|
||||
u16 e_shnum; /* Number of section header entries */
|
||||
u16 e_shstrndx; /* Section name string table index */
|
||||
};
|
||||
|
||||
typedef enum : u32 {
|
||||
PT_NULL = 0x0,
|
||||
PT_LOAD = 0x1,
|
||||
PT_DYNAMIC = 0x2,
|
||||
PT_INTERP = 0x3,
|
||||
PT_NOTE = 0x4,
|
||||
PT_SHLIB = 0x5,
|
||||
PT_PHDR = 0x6,
|
||||
PT_TLS = 0x7,
|
||||
PT_NUM = 0x8,
|
||||
PT_SCE_RELA = 0x60000000,
|
||||
PT_SCE_DYNLIBDATA = 0x61000000,
|
||||
PT_SCE_PROCPARAM = 0x61000001,
|
||||
PT_SCE_MODULE_PARAM = 0x61000002,
|
||||
PT_SCE_RELRO = 0x61000010,
|
||||
PT_GNU_EH_FRAME = 0x6474e550,
|
||||
PT_GNU_STACK = 0x6474e551,
|
||||
PT_GNU_RELRO = 0x6474e552,
|
||||
PT_SCE_COMMENT = 0x6fffff00,
|
||||
PT_SCE_LIBVERSION = 0x6fffff01,
|
||||
PT_LOSUNW = 0x6ffffffa,
|
||||
PT_SUNWBSS = 0x6ffffffa,
|
||||
PT_SUNWSTACK = 0x6ffffffb,
|
||||
PT_HISUNW = 0x6fffffff,
|
||||
PT_HIOS = 0x6fffffff,
|
||||
PT_LOPROC = 0x70000000,
|
||||
PT_HIPROC = 0x7fffffff
|
||||
} elf_program_type;
|
||||
|
||||
typedef enum : u32 {
|
||||
PF_NONE = 0x0,
|
||||
PF_EXEC = 0x1,
|
||||
PF_WRITE = 0x2,
|
||||
PF_WRITE_EXEC = 0x3,
|
||||
PF_READ = 0x4,
|
||||
PF_READ_EXEC = 0x5,
|
||||
PF_READ_WRITE = 0x6,
|
||||
PF_READ_WRITE_EXEC = 0x7
|
||||
} elf_program_flags;
|
||||
|
||||
struct elf_program_header
|
||||
{
|
||||
elf_program_type p_type; /* Type of segment */
|
||||
elf_program_flags p_flags; /* Segment attributes */
|
||||
u64 p_offset; /* Offset in file */
|
||||
u64 p_vaddr; /* Virtual address in memory */
|
||||
u64 p_paddr; /* Reserved */
|
||||
u64 p_filesz; /* Size of segment in file */
|
||||
u64 p_memsz; /* Size of segment in memory */
|
||||
u64 p_align; /* Alignment of segment */
|
||||
};
|
||||
|
||||
struct elf_section_header
|
||||
{
|
||||
u32 sh_name; /* Section name */
|
||||
u32 sh_type; /* Section type */
|
||||
u64 sh_flags; /* Section attributes */
|
||||
u64 sh_addr; /* Virtual address in memory */
|
||||
u64 sh_offset; /* Offset in file */
|
||||
u64 sh_size; /* Size of section */
|
||||
u32 sh_link; /* Link to other section */
|
||||
u32 sh_info; /* Miscellaneous information */
|
||||
u64 sh_addralign; /* Address alignment boundary */
|
||||
u64 sh_entsize; /* Size of entries, if section has table */
|
||||
};
|
||||
|
||||
typedef enum :u64 {
|
||||
PT_FAKE = 0x1,
|
||||
PT_NPDRM_EXEC = 0x4,
|
||||
PT_NPDRM_DYNLIB = 0x5,
|
||||
PT_SYSTEM_EXEC = 0x8,
|
||||
PT_SYSTEM_DYNLIB = 0x9,
|
||||
PT_HOST_KERNEL = 0xC,
|
||||
PT_SECURE_MODULE = 0xE,
|
||||
PT_SECURE_KERNEL = 0xF
|
||||
} program_type_es;
|
||||
|
||||
struct elf_program_id_header
|
||||
{
|
||||
u64 authid;
|
||||
program_type_es program_type;
|
||||
u64 appver;
|
||||
u64 firmver;
|
||||
u08 digest[32];
|
||||
};
|
||||
|
||||
constexpr s64 DT_NULL = 0;
|
||||
constexpr s64 DT_NEEDED = 0x00000001;
|
||||
constexpr s64 DT_RELA = 0x00000007;
|
||||
constexpr s64 DT_INIT = 0x0000000c;
|
||||
constexpr s64 DT_FINI = 0x0000000d;
|
||||
constexpr s64 DT_DEBUG = 0x00000015;
|
||||
constexpr s64 DT_TEXTREL = 0x00000016;
|
||||
constexpr s64 DT_INIT_ARRAY = 0x00000019;
|
||||
constexpr s64 DT_FINI_ARRAY = 0x0000001a;
|
||||
constexpr s64 DT_INIT_ARRAYSZ = 0x0000001b;
|
||||
constexpr s64 DT_FINI_ARRAYSZ = 0x0000001c;
|
||||
constexpr s64 DT_FLAGS = 0x0000001e;
|
||||
constexpr s64 DT_PREINIT_ARRAY = 0x00000020;
|
||||
constexpr s64 DT_PREINIT_ARRAYSZ = 0x00000021;
|
||||
constexpr s64 DT_SCE_FINGERPRINT = 0x61000007;
|
||||
constexpr s64 DT_SCE_ORIGINAL_FILENAME = 0x61000009;
|
||||
constexpr s64 DT_SCE_MODULE_INFO = 0x6100000d;
|
||||
constexpr s64 DT_SCE_NEEDED_MODULE = 0x6100000f;
|
||||
constexpr s64 DT_SCE_MODULE_ATTR = 0x61000011;
|
||||
constexpr s64 DT_SCE_EXPORT_LIB = 0x61000013;
|
||||
constexpr s64 DT_SCE_IMPORT_LIB = 0x61000015;
|
||||
constexpr s64 DT_SCE_IMPORT_LIB_ATTR = 0x61000019;
|
||||
constexpr s64 DT_SCE_HASH = 0x61000025;
|
||||
constexpr s64 DT_SCE_PLTGOT = 0x61000027;
|
||||
constexpr s64 DT_SCE_JMPREL = 0x61000029;
|
||||
constexpr s64 DT_SCE_PLTREL = 0x6100002b;
|
||||
constexpr s64 DT_SCE_PLTRELSZ = 0x6100002d;
|
||||
constexpr s64 DT_SCE_RELA = 0x6100002f;
|
||||
constexpr s64 DT_SCE_RELASZ = 0x61000031;
|
||||
constexpr s64 DT_SCE_RELAENT = 0x61000033;
|
||||
constexpr s64 DT_SCE_SYMENT = 0x6100003b;
|
||||
constexpr s64 DT_SCE_HASHSZ = 0x6100003d;
|
||||
constexpr s64 DT_SCE_STRTAB = 0x61000035;
|
||||
constexpr s64 DT_SCE_STRSZ = 0x61000037;
|
||||
constexpr s64 DT_SCE_SYMTAB = 0x61000039;
|
||||
constexpr s64 DT_SCE_SYMTABSZ = 0x6100003f;
|
||||
|
||||
|
||||
struct elf_dynamic
|
||||
{
|
||||
s64 d_tag;
|
||||
union
|
||||
{
|
||||
u64 d_val;
|
||||
u64 d_ptr;
|
||||
} d_un;
|
||||
};
|
||||
|
||||
constexpr u08 STB_LOCAL = 0;
|
||||
constexpr u08 STB_GLOBAL = 1;
|
||||
constexpr u08 STB_WEAK = 2;
|
||||
|
||||
constexpr u08 STT_NOTYPE = 0;
|
||||
constexpr u08 STT_OBJECT = 1;
|
||||
constexpr u08 STT_FUN = 2;
|
||||
constexpr u08 STT_SECTION = 3;
|
||||
constexpr u08 STT_FILE = 4;
|
||||
constexpr u08 STT_COMMON = 5;
|
||||
constexpr u08 STT_TLS = 6;
|
||||
constexpr u08 STT_LOOS = 10;
|
||||
constexpr u08 STT_SCE = 11; //module_start/module_stop
|
||||
constexpr u08 STT_HIOS = 12;
|
||||
constexpr u08 STT_LOPRO = 13;
|
||||
constexpr u08 STT_SPARC_REGISTER = 13;
|
||||
constexpr u08 STT_HIPROC = 15;
|
||||
|
||||
constexpr u08 STV_DEFAULT = 0;
|
||||
constexpr u08 STV_INTERNAL = 1;
|
||||
constexpr u08 STV_HIDDEN = 2;
|
||||
constexpr u08 STV_PROTECTED = 3;
|
||||
|
||||
struct elf_symbol
|
||||
{
|
||||
u08 GetBind() const { return st_info >> 4u; }
|
||||
u08 GetType() const { return st_info & 0xfu; }
|
||||
u08 GetVisibility() const { return st_other & 3u; }
|
||||
|
||||
u32 st_name;
|
||||
u08 st_info;
|
||||
u08 st_other;
|
||||
u16 st_shndx;
|
||||
u64 st_value;
|
||||
u64 st_size;
|
||||
};
|
||||
|
||||
struct elf_relocation
|
||||
{
|
||||
u32 GetSymbol() const { return static_cast<u32>(rel_info >> 32u); }
|
||||
u32 GetType() const { return static_cast<u32>(rel_info & 0xffffffff); }
|
||||
|
||||
u64 rel_offset;
|
||||
u64 rel_info;
|
||||
s64 rel_addend;
|
||||
};
|
||||
constexpr u32 R_X86_64_64 = 1; // Direct 64 bit
|
||||
constexpr u32 R_X86_64_JUMP_SLOT = 7; // Create PLT entry
|
||||
constexpr u32 R_X86_64_RELATIVE = 8; // Adjust by program base
|
||||
|
||||
class Elf {
|
||||
public:
|
||||
Elf() = default;
|
||||
virtual ~Elf();
|
||||
|
||||
void Open(const std::string & file_name);
|
||||
bool isSelfFile() const;
|
||||
bool isElfFile() const;
|
||||
void DebugDump();
|
||||
|
||||
[[nodiscard]] self_header GetSElfHeader() const {
|
||||
return m_self;
|
||||
}
|
||||
|
||||
[[nodiscard]] elf_header GetElfHeader() const {
|
||||
return m_elf_header;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<const elf_program_header> GetProgramHeader() const {
|
||||
return m_elf_phdr;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<const self_segment_header> GetSegmentHeader() const {
|
||||
return m_self_segments;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 GetElfEntry() const {
|
||||
return m_elf_header.e_entry;
|
||||
}
|
||||
|
||||
std::string SElfHeaderStr();
|
||||
std::string SELFSegHeader(u16 no);
|
||||
std::string ElfHeaderStr();
|
||||
std::string ElfPHeaderStr(u16 no);
|
||||
std::string ElfPheaderTypeStr(u32 type);
|
||||
std::string ElfPheaderFlagsStr(u32 flags);
|
||||
|
||||
void LoadSegment(u64 virtual_addr, u64 file_offset, u64 size);
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
Common::FS::File m_f{};
|
||||
bool is_self{};
|
||||
self_header m_self{};
|
||||
std::vector<self_segment_header> m_self_segments;
|
||||
elf_header m_elf_header{};
|
||||
std::vector<elf_program_header> m_elf_phdr;
|
||||
std::vector<elf_section_header> m_elf_shdr;
|
||||
elf_program_id_header m_self_id_header{};
|
||||
};
|
29
src/core/PS4/Loader/SymbolsResolver.cpp
Normal file
29
src/core/PS4/Loader/SymbolsResolver.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "../../../types.h"
|
||||
#include "SymbolsResolver.h"
|
||||
#include <Util/log.h>
|
||||
|
||||
void SymbolsResolver::AddSymbol(const SymbolRes& s, u64 virtual_addr)
|
||||
{
|
||||
SymbolRecord r{};
|
||||
r.name = GenerateName(s);
|
||||
r.virtual_address = virtual_addr;
|
||||
m_symbols.push_back(r);
|
||||
}
|
||||
|
||||
std::string SymbolsResolver::GenerateName(const SymbolRes& s) {
|
||||
return fmt::format("{} lib[{}_v{}]mod[{}_v{}.{}]",
|
||||
s.name, s.library, s.library_version,
|
||||
s.module, s.module_version_major, s.module_version_minor);
|
||||
}
|
||||
|
||||
const SymbolRecord* SymbolsResolver::FindSymbol(const SymbolRes& s) const {
|
||||
const std::string name = GenerateName(s);
|
||||
for (u32 i = 0; i < m_symbols.size(); i++) {
|
||||
if (m_symbols[i].name.compare(name) == 0) {
|
||||
return &m_symbols[i];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Unresolved! {}\n", name);
|
||||
return nullptr;
|
||||
}
|
40
src/core/PS4/Loader/SymbolsResolver.h
Normal file
40
src/core/PS4/Loader/SymbolsResolver.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "../../../types.h"
|
||||
|
||||
|
||||
struct SymbolRecord
|
||||
{
|
||||
std::string name;
|
||||
u64 virtual_address;
|
||||
};
|
||||
|
||||
struct SymbolRes
|
||||
{
|
||||
std::string name;
|
||||
std::string nidName;
|
||||
std::string library;
|
||||
u16 library_version;
|
||||
std::string module;
|
||||
u08 module_version_major;
|
||||
u08 module_version_minor;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
class SymbolsResolver
|
||||
{
|
||||
public:
|
||||
SymbolsResolver() = default;
|
||||
virtual ~SymbolsResolver() = default;
|
||||
|
||||
void AddSymbol(const SymbolRes& s, u64 virtual_addr);
|
||||
const SymbolRecord* FindSymbol(const SymbolRes& s) const;
|
||||
|
||||
static std::string GenerateName(const SymbolRes& s);
|
||||
|
||||
private:
|
||||
std::vector<SymbolRecord> m_symbols;
|
||||
};
|
80
src/core/PS4/Stubs.cpp
Normal file
80
src/core/PS4/Stubs.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#include "Stubs.h"
|
||||
|
||||
#include "Util/aerolib.h"
|
||||
|
||||
#include "Util/log.h"
|
||||
|
||||
// Helper to provide stub implementations for missing functions
|
||||
//
|
||||
// This works by pre-compiling generic stub functions ("slots"), and then
|
||||
// on lookup, setting up the nid_entry they are matched with
|
||||
//
|
||||
// If it runs out of stubs with name information, it will return
|
||||
// a default implemetnation without function name details
|
||||
|
||||
// Up to 512, larger values lead to more resolve stub slots
|
||||
// and to longer compile / CI times
|
||||
//
|
||||
// Must match STUBS_LIST define
|
||||
#define MAX_STUBS 128
|
||||
|
||||
u64 UnresolvedStub() {
|
||||
LOG_ERROR("Unresolved Stub: called, returning zero to {}\n", __builtin_return_address(0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 UnknownStub() {
|
||||
LOG_ERROR("Stub: Unknown (nid: Unknown) called, returning zero to {}\n", __builtin_return_address(0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static aerolib::nid_entry* stub_nids[MAX_STUBS];
|
||||
static std::string stub_nids_unknown[MAX_STUBS];
|
||||
|
||||
template <int stub_index>
|
||||
static u64 CommonStub() {
|
||||
auto entry = stub_nids[stub_index];
|
||||
if (entry) {
|
||||
LOG_ERROR("Stub: {} (nid: {}) called, returning zero to {}\n", entry->name, entry->nid, __builtin_return_address(0));
|
||||
} else {
|
||||
LOG_ERROR("Stub: Unknown (nid: {}) called, returning zero to {}\n", stub_nids_unknown[stub_index], __builtin_return_address(0));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 UsedStubEntries;
|
||||
|
||||
#define XREP_1(x) \
|
||||
&CommonStub<x>,
|
||||
|
||||
#define XREP_2(x) XREP_1(x) XREP_1(x + 1)
|
||||
#define XREP_4(x) XREP_2(x) XREP_2(x + 2)
|
||||
#define XREP_8(x) XREP_4(x) XREP_4(x + 4)
|
||||
#define XREP_16(x) XREP_8(x) XREP_8(x + 8)
|
||||
#define XREP_32(x) XREP_16(x) XREP_16(x + 16)
|
||||
#define XREP_64(x) XREP_32(x) XREP_32(x + 32)
|
||||
#define XREP_128(x) XREP_64(x) XREP_64(x + 64)
|
||||
#define XREP_256(x) XREP_128(x) XREP_128(x + 128)
|
||||
#define XREP_512(x) XREP_256(x) XREP_256(x + 256)
|
||||
|
||||
#define STUBS_LIST XREP_128(0)
|
||||
|
||||
static u64 (*stub_handlers[MAX_STUBS])() = {
|
||||
STUBS_LIST
|
||||
};
|
||||
|
||||
u64 GetStub(const char* nid) {
|
||||
if (UsedStubEntries >= MAX_STUBS) {
|
||||
return (u64)&UnknownStub;
|
||||
}
|
||||
|
||||
auto entry = aerolib::find_by_nid(nid);
|
||||
if (!entry) {
|
||||
stub_nids_unknown[UsedStubEntries] = nid;
|
||||
} else {
|
||||
stub_nids[UsedStubEntries] = entry;
|
||||
}
|
||||
|
||||
return (u64)stub_handlers[UsedStubEntries++];
|
||||
}
|
4
src/core/PS4/Stubs.h
Normal file
4
src/core/PS4/Stubs.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#include "types.h"
|
||||
|
||||
u64 UnresolvedStub();
|
||||
u64 GetStub(const char *nid);
|
38
src/core/PS4/Util/aerolib.cpp
Normal file
38
src/core/PS4/Util/aerolib.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "aerolib.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "Util/log.h"
|
||||
|
||||
namespace aerolib {
|
||||
|
||||
// Use a direct table here + binary search as contents are static
|
||||
nid_entry nids[] = {
|
||||
#define STUB(nid, name) \
|
||||
{ nid, #name },
|
||||
#include "aerolib.inl"
|
||||
#undef STUB
|
||||
};
|
||||
|
||||
nid_entry* find_by_nid(const char* nid) {
|
||||
s64 l = 0;
|
||||
s64 r = sizeof(nids) / sizeof(nids[0]) - 1;
|
||||
|
||||
while (l <= r) {
|
||||
size_t m = l + (r - l) / 2;
|
||||
|
||||
int cmp = strcmp(nids[m].nid, nid);
|
||||
|
||||
if (cmp == 0)
|
||||
return &nids[m];
|
||||
else if (cmp < 0)
|
||||
l = m + 1;
|
||||
else
|
||||
r = m - 1;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace aerolib
|
11
src/core/PS4/Util/aerolib.h
Normal file
11
src/core/PS4/Util/aerolib.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include <stdint.h>
|
||||
|
||||
namespace aerolib {
|
||||
|
||||
struct nid_entry {
|
||||
const char* nid;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
nid_entry* find_by_nid(const char* nid);
|
||||
};
|
11225
src/core/PS4/Util/aerolib.inl
Normal file
11225
src/core/PS4/Util/aerolib.inl
Normal file
File diff suppressed because it is too large
Load diff
29
src/core/hle/libraries/libkernel/file_system.cpp
Normal file
29
src/core/hle/libraries/libkernel/file_system.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "file_system.h"
|
||||
|
||||
#include <core/PS4/HLE/Libs.h>
|
||||
#include <Util/log.h>
|
||||
#include <debug.h>
|
||||
|
||||
namespace Core::Libraries::LibKernel {
|
||||
constexpr bool log_file_fs = true; // disable it to disable logging
|
||||
|
||||
int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
|
||||
LOG_INFO_IF(log_file_fs, "sceKernelOpen path = {} flags = {:#x} mode = {:#x}\n", path, flags, mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI open(const char* path, int flags, /* SceKernelMode*/ u16 mode) {
|
||||
LOG_INFO_IF(log_file_fs, "posix open redirect to sceKernelOpen\n");
|
||||
int result = sceKernelOpen(path, flags, mode);
|
||||
if (result < 0) {
|
||||
BREAKPOINT(); // posix calls different only for their return values
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void fileSystemSymbolsRegister(SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen);
|
||||
LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, open);
|
||||
}
|
||||
|
||||
} // namespace Core::Libraries::LibKernel
|
13
src/core/hle/libraries/libkernel/file_system.h
Normal file
13
src/core/hle/libraries/libkernel/file_system.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include <types.h>
|
||||
#include "core/PS4/Loader/SymbolsResolver.h"
|
||||
|
||||
namespace Core::Libraries::LibKernel {
|
||||
int PS4_SYSV_ABI sceKernelOpen(const char *path, int flags, /* SceKernelMode*/ u16 mode);
|
||||
|
||||
// posix file system
|
||||
int PS4_SYSV_ABI open(const char *path, int flags, /* SceKernelMode*/ u16 mode);
|
||||
|
||||
|
||||
void fileSystemSymbolsRegister(SymbolsResolver *sym);
|
||||
} // namespace Core::Libraries::LibKernel
|
24
src/core/hle/libraries/libkernel/time_management.cpp
Normal file
24
src/core/hle/libraries/libkernel/time_management.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include "time_management.h"
|
||||
|
||||
#include <core/PS4/HLE/Libs.h>
|
||||
#include "Lib/Timer.h"
|
||||
#include "emuTimer.h"
|
||||
|
||||
namespace Core::Libraries::LibKernel {
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTime() {
|
||||
return static_cast<u64>(Emulator::emuTimer::getTimeMsec() * 1000.0); // return time in microseconds
|
||||
}
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter() { return Emulator::emuTimer::getTimeCounter(); }
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency() { return Emulator::emuTimer::getTimeFrequency(); }
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelReadTsc() { return Lib::Timer::getQueryPerformanceCounter(); }
|
||||
|
||||
void timeSymbolsRegister(SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("4J2sUJmuHZQ", "libkernel", 1, "libkernel", 1, 1, sceKernelGetProcessTime);
|
||||
LIB_FUNCTION("fgxnMeTNUtY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetProcessTimeCounter);
|
||||
LIB_FUNCTION("BNowx2l588E", "libkernel", 1, "libkernel", 1, 1, sceKernelGetProcessTimeCounterFrequency);
|
||||
LIB_FUNCTION("-2IRUCO--PM", "libkernel", 1, "libkernel", 1, 1, sceKernelReadTsc);
|
||||
}
|
||||
|
||||
} // namespace Core::Libraries::LibKernel
|
13
src/core/hle/libraries/libkernel/time_management.h
Normal file
13
src/core/hle/libraries/libkernel/time_management.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "core/PS4/Loader/SymbolsResolver.h"
|
||||
|
||||
namespace Core::Libraries::LibKernel {
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTime();
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter();
|
||||
u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency();
|
||||
u64 PS4_SYSV_ABI sceKernelReadTsc();
|
||||
|
||||
void timeSymbolsRegister(SymbolsResolver* sym);
|
||||
}
|
143
src/core/virtual_memory.cpp
Normal file
143
src/core/virtual_memory.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#include "virtual_memory.h"
|
||||
|
||||
#include "core/PS4/Loader/Elf.h"
|
||||
|
||||
#ifdef _WIN64
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN64)
|
||||
enum PosixPageProtection {
|
||||
PAGE_NOACCESS = 0,
|
||||
PAGE_READONLY = PROT_READ,
|
||||
PAGE_READWRITE = PROT_READ | PROT_WRITE,
|
||||
PAGE_EXECUTE = PROT_EXEC,
|
||||
PAGE_EXECUTE_READ = PROT_EXEC | PROT_READ,
|
||||
PAGE_EXECUTE_READWRITE = PROT_EXEC | PROT_READ | PROT_WRITE
|
||||
};
|
||||
#endif
|
||||
|
||||
#include <debug.h>
|
||||
|
||||
#include "../Util/Log.h"
|
||||
|
||||
namespace VirtualMemory {
|
||||
static u32 convertMemoryMode(MemoryMode mode) {
|
||||
switch (mode) {
|
||||
case MemoryMode::Read: return PAGE_READONLY;
|
||||
case MemoryMode::Write:
|
||||
case MemoryMode::ReadWrite: return PAGE_READWRITE;
|
||||
|
||||
case MemoryMode::Execute: return PAGE_EXECUTE;
|
||||
case MemoryMode::ExecuteRead: return PAGE_EXECUTE_READ;
|
||||
case MemoryMode::ExecuteWrite:
|
||||
case MemoryMode::ExecuteReadWrite: return PAGE_EXECUTE_READWRITE;
|
||||
|
||||
case MemoryMode::NoAccess: return PAGE_NOACCESS;
|
||||
default: return PAGE_NOACCESS;
|
||||
}
|
||||
}
|
||||
static MemoryMode convertMemoryMode(u32 mode) {
|
||||
switch (mode) {
|
||||
case PAGE_NOACCESS: return MemoryMode::NoAccess;
|
||||
case PAGE_READONLY: return MemoryMode::Read;
|
||||
case PAGE_READWRITE: return MemoryMode::ReadWrite;
|
||||
case PAGE_EXECUTE: return MemoryMode::Execute;
|
||||
case PAGE_EXECUTE_READ: return MemoryMode::ExecuteRead;
|
||||
case PAGE_EXECUTE_READWRITE: return MemoryMode::ExecuteReadWrite;
|
||||
default: return MemoryMode::NoAccess;
|
||||
}
|
||||
}
|
||||
|
||||
u64 memory_alloc(u64 address, u64 size, MemoryMode mode) {
|
||||
#ifdef _WIN64
|
||||
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), convertMemoryMode(mode)));
|
||||
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<u32>(GetLastError());
|
||||
LOG_ERROR_IF(true, "VirtualAlloc() failed: 0x{:X}\n", err);
|
||||
}
|
||||
#else
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
mmap(reinterpret_cast<void*>(static_cast<uintptr_t>(address)), size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||
|
||||
if (ptr == reinterpret_cast<uintptr_t> MAP_FAILED) {
|
||||
LOG_ERROR_IF(true, "mmap() failed: {}\n", std::strerror(errno));
|
||||
}
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
bool memory_protect(u64 address, u64 size, MemoryMode mode, MemoryMode* old_mode) {
|
||||
#ifdef _WIN64
|
||||
DWORD old_protect = 0;
|
||||
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size, convertMemoryMode(mode), &old_protect) == 0) {
|
||||
auto err = static_cast<u32>(GetLastError());
|
||||
LOG_ERROR_IF(true, "VirtualProtect() failed: 0x{:X}\n", err);
|
||||
return false;
|
||||
}
|
||||
if (old_mode != nullptr) {
|
||||
*old_mode = convertMemoryMode(old_protect);
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
#error Unimplement memory_protect function
|
||||
#endif
|
||||
}
|
||||
|
||||
bool memory_flush(u64 address, u64 size) {
|
||||
#ifdef _WIN64
|
||||
if (::FlushInstructionCache(GetCurrentProcess(), reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size) == 0) {
|
||||
auto err = static_cast<u32>(GetLastError());
|
||||
LOG_ERROR_IF(true, "FlushInstructionCache() failed: 0x{:X}\n", err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else // linux probably doesn't have something similar
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
bool memory_patch(u64 vaddr, u64 value) {
|
||||
MemoryMode old_mode{};
|
||||
memory_protect(vaddr, 8, MemoryMode::ReadWrite, &old_mode);
|
||||
|
||||
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
|
||||
|
||||
bool ret = (*ptr != value);
|
||||
|
||||
*ptr = value;
|
||||
|
||||
memory_protect(vaddr, 8, old_mode, nullptr);
|
||||
|
||||
// if mode is executable flush it so insure that cpu finds it
|
||||
if ((old_mode == MemoryMode::Execute || old_mode == MemoryMode::ExecuteRead || old_mode == MemoryMode::ExecuteWrite ||
|
||||
old_mode == MemoryMode::ExecuteReadWrite)) {
|
||||
memory_flush(vaddr, 8);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
static u64 AlignUp(u64 pos, u64 align) { return (align != 0 ? (pos + (align - 1)) & ~(align - 1) : pos); }
|
||||
|
||||
u64 memory_alloc_aligned(u64 address, u64 size, MemoryMode mode, u64 alignment) {
|
||||
// try allocate aligned address inside user area
|
||||
MEM_ADDRESS_REQUIREMENTS req{};
|
||||
MEM_EXTENDED_PARAMETER param{};
|
||||
req.LowestStartingAddress = (address == 0 ? reinterpret_cast<PVOID>(USER_MIN) : reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
|
||||
req.Alignment = alignment;
|
||||
param.Type = MemExtendedParameterAddressRequirements;
|
||||
param.Pointer = &req;
|
||||
|
||||
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc2(
|
||||
GetCurrentProcess(), nullptr, size, static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), convertMemoryMode(mode), ¶m, 1));
|
||||
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<u32>(GetLastError());
|
||||
LOG_ERROR_IF(true, "VirtualAlloc2() failed: 0x{:X}\n", err);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
} // namespace VirtualMemory
|
28
src/core/virtual_memory.h
Normal file
28
src/core/virtual_memory.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
#include <types.h>
|
||||
|
||||
constexpr u64 SYSTEM_RESERVED = 0x800000000u;
|
||||
constexpr u64 CODE_BASE_OFFSET = 0x100000000u;
|
||||
constexpr u64 SYSTEM_MANAGED_MIN = 0x0000040000u;
|
||||
constexpr u64 SYSTEM_MANAGED_MAX = 0x07FFFFBFFFu;
|
||||
constexpr u64 USER_MIN = 0x1000000000u;
|
||||
constexpr u64 USER_MAX = 0xFBFFFFFFFFu;
|
||||
|
||||
namespace VirtualMemory {
|
||||
enum class MemoryMode : u32 {
|
||||
NoAccess = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = 3,
|
||||
Execute = 4,
|
||||
ExecuteRead = 5,
|
||||
ExecuteWrite = 6,
|
||||
ExecuteReadWrite = 7,
|
||||
};
|
||||
u64 memory_alloc(u64 address, u64 size, MemoryMode mode);
|
||||
u64 memory_alloc_aligned(u64 address, u64 size, MemoryMode mode, u64 alignment);
|
||||
bool memory_protect(u64 address, u64 size, MemoryMode mode, MemoryMode* old_mode);
|
||||
bool memory_flush(u64 address, u64 size);
|
||||
bool memory_patch(u64 vaddr, u64 value);
|
||||
|
||||
} // namespace VirtualMemory
|
Loading…
Add table
Add a link
Reference in a new issue