core renaming

This commit is contained in:
georgemoralis 2023-10-31 14:28:42 +02:00
parent aee25dcaf9
commit 53a8024e43
54 changed files with 0 additions and 0 deletions

93
src/core/FsFile.cpp Normal file
View 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
View 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

View 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++;
}
}

View 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

View 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

View 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);
}

View 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; }

View 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

View 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

View 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

View 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

View 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

View 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);
}
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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, &param) : 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

View 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

View 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

View file

@ -0,0 +1,6 @@
#pragma once
#include <types.h>
namespace HLE::Libs::LibKernel::CPUManagement {
int PS4_SYSV_ABI sceKernelIsNeoMode();
};

View 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

View 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

View 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

View 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
View 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
View 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);
};

View 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

View 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

View 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);
}
};

View 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
View 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
View 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);
}

View 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
View 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
View 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
View 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
View 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{};
};

View 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;
}

View 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
View 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
View file

@ -0,0 +1,4 @@
#include "types.h"
u64 UnresolvedStub();
u64 GetStub(const char *nid);

View 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

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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
View 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), &param, 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
View 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