diff --git a/.vscode/settings.json b/.vscode/settings.json index eaeef112..677672c4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,7 +19,6 @@ "cmake.sourceDirectory": "${workspaceFolder}", "cmake.buildDirectory": "${workspaceFolder}/_build/_Release", "cmake.configureArgs": [ - // "-DBUILD_CORE=1", "-DISDEBUG=1" ], "cmake.configureSettings": { diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a6d865e..d0bb67e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) set(PSOFF_LIB_VERSION v.0.3) -set(PSOFF_RENDER_VERSION v.0.5) +set(PSOFF_RENDER_VERSION v.0.6-nightly_03.06.2024) set(ProjectName psOff_${CMAKE_BUILD_TYPE}) project(${ProjectName} VERSION 0.0.1) @@ -89,11 +89,11 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) ) endif() -# set(TEST_BENCH OFF CACHE BOOL "Enable testing") +set(TEST_BENCH OFF CACHE BOOL "Enable testing") -# if(TEST_BENCH) -# add_subdirectory(tests) -# endif() +if(TEST_BENCH) + add_subdirectory(tests) +endif() # include before link_libraries add_subdirectory(tools/logging) @@ -113,12 +113,7 @@ add_compile_definitions(IMAGE_BASE=${IMAGE_BASE}) add_subdirectory(modules) add_subdirectory(utility) add_subdirectory(tools/gamereport) - -if(BUILD_CORE) - add_subdirectory(core) -else() - add_custom_target(core) -endif() +add_subdirectory(core) # #- Projects diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 00000000..ef38e3a1 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,85 @@ +# Place before add_subdir -> define for all childs +add_compile_definitions( + BOOST_ALL_NO_LIB +) + +set(DST_INCLUDE_DIR "${CMAKE_BINARY_DIR}/core/export/include/core/") + +# Add directories +add_subdirectory(kernel) +add_subdirectory(videoout) +add_subdirectory(initParams) +add_subdirectory(timer) +add_subdirectory(systemContent) +add_subdirectory(networking) +add_subdirectory(hotkeys) +add_subdirectory(trophies) +add_subdirectory(fileManager) +add_subdirectory(memory) +add_subdirectory(dmem) +add_subdirectory(unwinding) +add_subdirectory(runtime) + +# Build +add_library(core SHARED + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ +) + +add_dependencies(core logging third_party config_emu psoff_render) +target_link_libraries(core PRIVATE + libboost_url + libboost_thread + libboost_chrono + libboost_program_options + libboost_filesystem + sdl2 + psoff_render.lib + OptickCore + psOff_utility + config_emu.lib + onecore.lib + IPHLPAPI.lib + Dbghelp.lib + wepoll.lib + Ws2_32.lib + libcrypto.lib + libssl.lib + pugixml.lib + ntdll.dll + imgui + VulkanMemoryAllocator + ${Vulkan_LIBRARIES} +) + +install(FILES $ DESTINATION debug OPTIONAL) +install(TARGETS core LIBRARY DESTINATION .) + +# Manage symbols +if(NOT ISDEBUG) + add_custom_command( + TARGET core POST_BUILD + COMMAND $<$:${CMAKE_STRIP}> + ARGS --strip-all $ + ) +endif() + +set_target_properties(core + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/core/export/lib" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/core/export/bin" + PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/" +) + +file(COPY imports/exports/pm4_custom.h DESTINATION ${DST_INCLUDE_DIR}/imports/exports) \ No newline at end of file diff --git a/core/dmem/CMakeLists.txt b/core/dmem/CMakeLists.txt new file mode 100644 index 00000000..f6728f11 --- /dev/null +++ b/core/dmem/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(dmem OBJECT + memoryManager.cpp + + types/directmem.cpp + types/flexiblemem.cpp +) + +add_dependencies(dmem third_party psOff_utility) + +target_include_directories(dmem PRIVATE + ${Vulkan_INCLUDE_DIRS} +) + +file(COPY memoryManager.h imemory.h DESTINATION ${DST_INCLUDE_DIR}/dmem) \ No newline at end of file diff --git a/core/dmem/imemory.h b/core/dmem/imemory.h new file mode 100644 index 00000000..d2185f98 --- /dev/null +++ b/core/dmem/imemory.h @@ -0,0 +1,40 @@ +#pragma once + +#include "utility/utility.h" + +struct SceKernelVirtualQueryInfo; +struct SceKernelDirectMemoryQueryInfo; + +class IMemoryType { + CLASS_NO_COPY(IMemoryType); + CLASS_NO_MOVE(IMemoryType); + + protected: + IMemoryType() {} + + public: + virtual ~IMemoryType() = default; + + /** + * @brief Only for init + * + */ + virtual void setTotalSize(uint64_t totalSize) = 0; + + virtual int alloc(size_t len, size_t alignment, int memoryType, uint64_t* outAddr) = 0; + virtual int free(off_t start, size_t len) = 0; + + virtual int map(uint64_t vaddr, off_t directMemoryOffset, size_t len, int prot, int flags, size_t alignment, uint64_t* outAddr) = 0; + virtual int unmap(uint64_t vaddr, uint64_t size) = 0; + + virtual int reserve(uint64_t start, size_t len, size_t alignment, int flags, uint64_t* outAddr) = 0; + + virtual uint64_t size() const = 0; + + virtual int getAvailableSize(uint32_t start, uint32_t end, size_t alignment, uint32_t* startOut, size_t* sizeOut) const = 0; + + virtual int32_t virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const = 0; + virtual int32_t directQuery(uint64_t offset, SceKernelDirectMemoryQueryInfo* info) const = 0; + + virtual void deinit() = 0; +}; \ No newline at end of file diff --git a/core/dmem/memoryManager.cpp b/core/dmem/memoryManager.cpp new file mode 100644 index 00000000..db53f637 --- /dev/null +++ b/core/dmem/memoryManager.cpp @@ -0,0 +1,124 @@ +#define __APICALL_EXTERN +#include "memoryManager.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "modules/libkernel/dmem.h" +#include "types/memory.h" + +#include +#include + +LOG_DEFINE_MODULE(MemoryManager); + +namespace { +struct MappingData { + MappingType type; + uint64_t size; +}; +} // namespace + +class MemoryManager: public IMemoryManager { + + std::unique_ptr m_directMemory; + std::unique_ptr m_flexibleMemory; + + mutable boost::mutex m_mutexInt; + + std::map m_mappings; + + std::map m_stackAddrMap; + + public: + MemoryManager() { + m_directMemory = createDirectMemory(this); + m_flexibleMemory = createFlexibleMemory(this); + } + + ~MemoryManager() = default; + + // ### Interface + void registerMapping(uint64_t vaddr, uint64_t size, MappingType type) final; + MappingType unregisterMapping(uint64_t vaddr) final; + + void registerStack(uint64_t addr, uint64_t size) final; + void unregisterStack(uint64_t addr) final; + + int32_t virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const final; + + IMemoryType* directMemory() final { return m_directMemory.get(); } + + IMemoryType* flexibleMemory() final { return m_flexibleMemory.get(); } +}; + +IMemoryManager* accessMemoryManager() { + static MemoryManager inst; + return &inst; +} + +void MemoryManager::registerMapping(uint64_t vaddr, uint64_t size, MappingType type) { + + boost::unique_lock lock(m_mutexInt); + + m_mappings.emplace(std::make_pair(vaddr, MappingData {.type = type, .size = size})); +} + +MappingType MemoryManager::unregisterMapping(uint64_t vaddr) { + boost::unique_lock lock(m_mutexInt); + + if (auto it = m_mappings.find((uint64_t)vaddr); it != m_mappings.end()) { + auto type = it->second.type; + m_mappings.erase(it); + return type; + } + + return MappingType::None; +} + +void MemoryManager::registerStack(uint64_t addr, uint64_t size) { + boost::unique_lock lock(m_mutexInt); + m_stackAddrMap[addr] = size; +} + +void MemoryManager::unregisterStack(uint64_t addr) { + boost::unique_lock lock(m_mutexInt); + m_stackAddrMap.erase(addr); +} + +int32_t MemoryManager::virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const { + LOG_USE_MODULE(MemoryManager); + + boost::unique_lock lock(m_mutexInt); + + auto itItem = m_mappings.lower_bound(addr); + + if (itItem == m_mappings.end() && addr > (itItem->first + itItem->second.size)) return getErr(ErrCode::_EACCES); // End reached + + if (itItem == m_mappings.end() || (itItem != m_mappings.begin() && itItem->first != addr)) --itItem; // Get the correct item + + int res = getErr(ErrCode::_EACCES); + + switch (itItem->second.type) { + case MappingType::Direct: { + res = m_directMemory->virtualQuery(itItem->first, info); + } break; + case MappingType::Flexible: { + res = m_flexibleMemory->virtualQuery(itItem->first, info); + } break; + case MappingType::Fixed: { + } break; + case MappingType::File: { + } break; + + default: break; + } + + if (res == Ok) { + LOG_TRACE(L"Query OK: addr:0x%08llx - start:0x%08llx end:0x%08llx prot:%d type:%d", itItem->first, info->start, info->end, info->protection, + info->memoryType); + } else { + LOG_TRACE(L"Query Error: addr:0x%08llx", addr); + } + + return res; +}; \ No newline at end of file diff --git a/core/dmem/memoryManager.h b/core/dmem/memoryManager.h new file mode 100644 index 00000000..07ce876a --- /dev/null +++ b/core/dmem/memoryManager.h @@ -0,0 +1,55 @@ +#pragma once + +#include "imemory.h" +#include "utility/utility.h" + +#include + +enum class MappingType { None, File, Flexible, Fixed, Direct }; + +struct SceKernelVirtualQueryInfo; + +class IMemoryManager { + CLASS_NO_COPY(IMemoryManager); + CLASS_NO_MOVE(IMemoryManager); + + public: + IMemoryManager() = default; + + virtual ~IMemoryManager() = default; + + /** + * @brief Register mapped memory + * + * @param vaddr + * @param type + */ + virtual void registerMapping(uint64_t vaddr, uint64_t size, MappingType type) = 0; + + /** + * @brief Unregisters mapping and returns the type of the mapping + * + * @param vaddr + * @return None: Mapping didn't exist + */ + virtual MappingType unregisterMapping(uint64_t vaddr) = 0; + + virtual void registerStack(uint64_t addr, uint64_t size) = 0; + virtual void unregisterStack(uint64_t addr) = 0; + + virtual int32_t virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const = 0; + + virtual IMemoryType* directMemory() = 0; + virtual IMemoryType* flexibleMemory() = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL IMemoryManager* accessMemoryManager(); +#undef __APICALL diff --git a/core/dmem/types/directmem.cpp b/core/dmem/types/directmem.cpp new file mode 100644 index 00000000..9bebb147 --- /dev/null +++ b/core/dmem/types/directmem.cpp @@ -0,0 +1,545 @@ +#include "../memoryManager.h" +#include "core/kernel/filesystem.h" +#include "core/runtime/procParam.h" +#include "core/runtime/runtimeLinker.h" +#include "core/videoout/videoout.h" +#include "logging.h" +#include "memory.h" +#include "modules/libkernel/codes.h" +#include "modules/libkernel/dmem.h" +#include "utility/utility.h" + +#include +#include +#include +#include +#include + +#undef min +LOG_DEFINE_MODULE(DirectMemory); + +namespace { +constexpr uint64_t DIRECTMEM_START = 0x100000000u; + +enum class MemoryState { + Free, + Reserved, + Commited, +}; + +struct MemoryMappingDirect { + uint64_t addr = 0; + + uint64_t heapAddr = 0; // addr for MemoryInfo + + uint64_t size = 0; + uint64_t alignment = 0; + + VmaVirtualAllocation vmaAlloc; +}; + +struct MemoryInfo { + uint64_t addr = 0; + void* allocAddr = nullptr; + + uint64_t size = 0; + uint64_t alignment = 0; + + int memoryType = 0; + int prot = 0; + + VmaVirtualAllocation vmaAlloc; + VmaVirtualBlock vmaBlock = nullptr; + + MemoryState state = MemoryState::Free; +}; + +bool checkIsGPU(int protection) { + return (protection & 0xf0) > 0; +} + +DWORD convProtection(int prot) { + switch (prot & 0xf) { + case 0: return PAGE_NOACCESS; + case 1: return PAGE_READONLY; + case 2: + case 3: return PAGE_READWRITE; + case 4: return PAGE_EXECUTE; + case 5: return PAGE_EXECUTE_READ; + case 6: + case 7: return PAGE_EXECUTE_READWRITE; + } + + if (checkIsGPU(prot)) { + return PAGE_READWRITE; // special case: cpu read/writes gpumemory on host memory + } + + return PAGE_NOACCESS; +} +} // namespace + +class DirectMemory: public IMemoryType { + mutable boost::mutex m_mutexInt; + + IMemoryManager* m_parent; + + std::map m_objects; + std::map m_mappings; + + uint64_t m_allocSize = 0; + uint64_t m_usedSize = 0; + + uint64_t m_sdkVersion = 0; + + VmaVirtualBlock m_virtualDeviceMemory; + + uint64_t getSDKVersion() { + if (m_sdkVersion == 0) { + auto* procParam = (ProcParam*)accessRuntimeLinker().mainModuleInfo().procParamAddr; + m_sdkVersion = procParam->header.sdkVersion; + } + + return m_sdkVersion; + } + + int retNoMem() { + if (m_sdkVersion < 0x3500000) return getErr(ErrCode::_EINVAL); + return getErr(ErrCode::_ENOMEM); + } + + MemoryInfo* getMemoryInfo(uint64_t len, uint64_t alignment, int prot, VmaVirtualAllocation& outAlloc, uint64_t& outAddr); + + public: + DirectMemory(IMemoryManager* parent): m_parent(parent) { + LOG_USE_MODULE(DirectMemory); + VmaVirtualBlockCreateInfo blockCreateInfo = { + .size = SCE_KERNEL_MAIN_DMEM_SIZE, + }; + + if (auto result = vmaCreateVirtualBlock(&blockCreateInfo, &m_virtualDeviceMemory); result != VK_SUCCESS) { + LOG_CRIT(L"vmaCreateVirtualBlock err:%S", string_VkResult(result)); + } + } + + virtual ~DirectMemory() { deinit(); } + + // ### Interface + + void setTotalSize(uint64_t totalSize) final; + + int alloc(size_t len, size_t alignment, int memoryType, uint64_t* outAddr) final; + int free(off_t start, size_t len) final; + + int map(uint64_t vaddr, off_t directMemoryOffset, size_t len, int prot, int flags, size_t alignment, uint64_t* outAddr) final; + int unmap(uint64_t vaddr, uint64_t size) final; + + virtual int reserve(uint64_t start, size_t len, size_t alignment, int flags, uint64_t* outAddr) final; + + uint64_t size() const final; + + int getAvailableSize(uint32_t start, uint32_t end, size_t alignment, uint32_t* startOut, size_t* sizeOut) const final; + + int32_t virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const final; + int32_t directQuery(uint64_t offset, SceKernelDirectMemoryQueryInfo* info) const final; + + void deinit() final; +}; + +std::unique_ptr createDirectMemory(IMemoryManager* parent) { + return std::make_unique(parent); +} + +void DirectMemory::setTotalSize(uint64_t totalSize) { + LOG_USE_MODULE(DirectMemory); + + vmaDestroyVirtualBlock(m_virtualDeviceMemory); + + VmaVirtualBlockCreateInfo blockCreateInfo = { + .size = totalSize, + }; + + if (auto result = vmaCreateVirtualBlock(&blockCreateInfo, &m_virtualDeviceMemory); result != VK_SUCCESS) { + LOG_CRIT(L"vmaCreateVirtualBlock err:%S", string_VkResult(result)); + } +} + +MemoryInfo* DirectMemory::getMemoryInfo(uint64_t len, uint64_t alignment, int prot, VmaVirtualAllocation& outAlloc, uint64_t& outAddr) { + if (m_objects.empty()) return nullptr; + + VmaVirtualAllocationCreateInfo allocCreateInfo = { + .size = len, + .alignment = alignment, + }; + + for (auto& item: m_objects) { + if (item.second.state == MemoryState::Reserved) continue; + + if (item.second.state == MemoryState::Commited && prot != item.second.prot) continue; + + if (auto result = vmaVirtualAllocate(item.second.vmaBlock, &allocCreateInfo, &outAlloc, &outAddr); result == VK_SUCCESS) { + return &item.second; + } + } + return nullptr; +} + +int DirectMemory::alloc(size_t len, size_t alignment, int memoryType, uint64_t* outAddr) { + LOG_USE_MODULE(DirectMemory); + + if ((alignment > 0 && !util::isPowerOfTwo(alignment)) || (len % SCE_KERNEL_PAGE_SIZE != 0) || (alignment % SCE_KERNEL_PAGE_SIZE != 0)) + return getErr(ErrCode::_EINVAL); + + boost::unique_lock lock(m_mutexInt); + + VmaVirtualAllocationCreateInfo allocCreateInfo = { + .size = len, + .alignment = alignment, + }; + + // Get block from device memory + VmaVirtualAllocation alloc; + + if (auto result = vmaVirtualAllocate(m_virtualDeviceMemory, &allocCreateInfo, &alloc, outAddr); result != VK_SUCCESS) { + LOG_ERR(L"Alloc Error| len:0x%08llx alignment:0x%08llx memorytype:%d err:%d", len, alignment, memoryType, GetLastError()); + return getErr(ErrCode::_ENOMEM); + } + // - check devicememory + + *outAddr += DIRECTMEM_START; // For debugging info + + // Device has space -> Create allocation + VmaVirtualBlockCreateInfo blockCreateInfo = { + .size = len, + }; + VmaVirtualBlock block; + + if (auto result = vmaCreateVirtualBlock(&blockCreateInfo, &block); result != VK_SUCCESS) { + LOG_ERR(L"Alloc Error| len:0x%08llx alignment:0x%08llx memorytype:%d err:%d", len, alignment, memoryType, GetLastError()); + return getErr(ErrCode::_ENOMEM); + } + // - + + m_allocSize += len; + m_objects.emplace(std::make_pair( + *outAddr, MemoryInfo {.addr = *outAddr, .size = len, .alignment = alignment, .memoryType = memoryType, .vmaAlloc = alloc, .vmaBlock = block})); + m_parent->registerMapping(*outAddr, len, MappingType::Direct); + LOG_DEBUG(L"-> Allocated: len:0x%08llx alignment:0x%08llx memorytype:%d -> 0x%08llx", len, alignment, memoryType, *outAddr); + + return Ok; +} + +int DirectMemory::free(off_t start, size_t len) { + LOG_USE_MODULE(DirectMemory); + + boost::unique_lock lock(m_mutexInt); + + uint64_t addr = DIRECTMEM_START + start; + + m_allocSize -= len; + + // Find the object + if (m_objects.empty()) return Ok; + + auto itHeap = m_objects.lower_bound(addr); + if (itHeap == m_objects.end() || (itHeap != m_objects.begin() && itHeap->first != addr)) --itHeap; // Get the correct item + + if (!(itHeap->first <= addr && (itHeap->first + itHeap->second.size >= addr))) return Ok; // Can't be found + //- + + LOG_DEBUG(L"free| addr:0x%08llx len:0x%08llx heap -> addr:0x%08llx len:0x%08llx", addr, len, itHeap->first, itHeap->second.size); + + // special: Check reserve if full reservation or commits + if (itHeap->second.state == MemoryState::Reserved) { + // todo + VirtualFree((void*)itHeap->second.addr, itHeap->second.size, 0); + return Ok; + } + + if (len != 0 && (itHeap->first != addr || itHeap->second.size != len)) { + LOG_ERR(L"free Error| start:0x%08llx len:0x%08llx != start:0x%08llx len:0x%08llx", addr, len, itHeap->first, itHeap->second.size); + } + + if (checkIsGPU(itHeap->second.prot)) { + // todo + + } else { + if (itHeap->second.allocAddr != 0) VirtualFree(itHeap->second.allocAddr, itHeap->second.size, 0); + } + + { + std::list dump; + for (auto& item: m_mappings) { + if (item.second.heapAddr == itHeap->first) { + m_usedSize -= item.second.size; + LOG_TRACE(L"Missing unmap for addr:0x%08llx len:0x%08llx, force unmap", item.second.addr, item.second.size); + + vmaVirtualFree(itHeap->second.vmaBlock, item.second.vmaAlloc); + m_parent->unregisterMapping(item.first); + dump.push_back(item.first); + } + + for (auto item: dump) { + m_mappings.erase(item); + } + } + } + + vmaDestroyVirtualBlock(itHeap->second.vmaBlock); + vmaVirtualFree(m_virtualDeviceMemory, itHeap->second.vmaAlloc); + + m_objects.erase(itHeap); + + return Ok; +} + +int DirectMemory::map(uint64_t vaddr, off_t offset, size_t len, int prot, int flags, size_t alignment, uint64_t* outAddr) { + LOG_USE_MODULE(DirectMemory); + + boost::unique_lock lock(m_mutexInt); + if (flags & (int)filesystem::SceMapMode::VOID_) { + LOG_CRIT(L"todo void map"); + } + + uint64_t desVaddr = 0; + if (flags & (int)filesystem::SceMapMode::FIXED) { + desVaddr = vaddr; + } + + VmaVirtualAllocation alloc = nullptr; + + // search for free space + uint64_t fakeAddrOffset = 0; + + MemoryInfo* info = nullptr; + if (flags & (int)filesystem::SceMapMode::FIXED) { + for (auto& item: m_objects) { + if (item.second.state == MemoryState::Reserved && item.first <= vaddr && item.second.size >= len) { + info = &item.second; + desVaddr = info->addr; + } + } + } + if (info == nullptr) info = getMemoryInfo(len, alignment, prot, alloc, fakeAddrOffset); + + if (info == nullptr) return retNoMem(); + // - + + // Check if Commit needed + if (info->state == MemoryState::Free || info->state == MemoryState::Reserved) { + MEM_ADDRESS_REQUIREMENTS addressReqs = {0}; + MEM_EXTENDED_PARAMETER extendedParams = {0}; + + addressReqs.Alignment = 0; // 64 KB alignment + addressReqs.LowestStartingAddress = (PVOID)DIRECTMEM_START; + addressReqs.HighestEndingAddress = (PVOID)0; + + extendedParams.Type = MemExtendedParameterAddressRequirements; + extendedParams.Pointer = &addressReqs; + + uint32_t flags = MEM_COMMIT; + if (info->state != MemoryState::Reserved) { + flags |= MEM_RESERVE | MEM_WRITE_WATCH; + } + void* ptr = VirtualAlloc2(NULL, (void*)desVaddr, info->size, flags, convProtection(prot), desVaddr != 0 ? 0 : &extendedParams, desVaddr != 0 ? 0 : 1); + if (ptr == 0) { + auto const err = GetLastError(); + LOG_ERR(L"Commit Error| addr:0x%08llx len:0x%08llx err:%d", info->addr, info->size, GetLastError()); + return getErr(ErrCode::_EINVAL); + } + + info->allocAddr = ptr; + info->state = MemoryState::Commited; + info->prot = prot; + LOG_DEBUG(L"Commit| addr:0x%08llx len:0x%08llx prot:%d ->", info->addr, info->size, prot, ptr); + } + // - commit + + *outAddr = (uint64_t)info->allocAddr + fakeAddrOffset; + m_mappings.emplace( + std::make_pair(*outAddr, MemoryMappingDirect {.addr = *outAddr, .heapAddr = info->addr, .size = len, .alignment = alignment, .vmaAlloc = alloc})); + + m_usedSize += len; + + LOG_DEBUG(L"-> Map: start:0x%08llx(0x%x) len:0x%08llx alignment:0x%08llx prot:%d -> 0x%08llx", vaddr, offset, len, alignment, prot, *outAddr); + + if (checkIsGPU(prot)) { + if (!accessVideoOut().notify_allocHeap(*outAddr, len, prot)) { + return getErr(ErrCode::_EINVAL); + } + } + + return Ok; +} + +int DirectMemory::reserve(uint64_t start, size_t len, size_t alignment, int flags, uint64_t* outAddr) { + LOG_USE_MODULE(DirectMemory); + + boost::unique_lock lock(m_mutexInt); + + MEM_ADDRESS_REQUIREMENTS addressReqs = {0}; + MEM_EXTENDED_PARAMETER extendedParams = {0}; + + addressReqs.Alignment = alignment; + addressReqs.LowestStartingAddress = (PVOID)0x100000000; + addressReqs.HighestEndingAddress = (PVOID)0; + + extendedParams.Type = MemExtendedParameterAddressRequirements; + extendedParams.Pointer = &addressReqs; + + *outAddr = (uint64_t)VirtualAlloc2(NULL, 0, len, MEM_RESERVE | MEM_WRITE_WATCH, PAGE_NOACCESS, &extendedParams, 1); + if (*outAddr == 0) { + auto const err = GetLastError(); + LOG_ERR(L"Reserve Error| addr:0x%08llx len:0x%08llx align:0x%08llx flags:0x%x err:%d", start, len, alignment, flags, GetLastError()); + return getErr(ErrCode::_EINVAL); + } + + LOG_DEBUG(L"-> Reserve: start:0x%08llx len:0x%08llx alignment:0x%08llx flags:0x%x -> 0x%08llx", start, len, alignment, flags, *outAddr); + + m_objects.emplace( + std::make_pair(*outAddr, MemoryInfo {.addr = *outAddr, .size = len, .alignment = alignment, .memoryType = 0, .state = MemoryState::Reserved})); + m_parent->registerMapping(*outAddr, len, MappingType::Direct); + return Ok; +} + +int DirectMemory::unmap(uint64_t vaddr, uint64_t size) { + LOG_USE_MODULE(DirectMemory); + + boost::unique_lock lock(m_mutexInt); + + // Find the object + if (m_mappings.empty()) return Ok; + + auto itItem = m_mappings.lower_bound(vaddr); + if (itItem == m_mappings.end() || (itItem != m_mappings.begin() && itItem->first != vaddr)) --itItem; // Get the correct item + + if (!(itItem->first <= vaddr && (itItem->first + itItem->second.size >= vaddr))) { + LOG_ERR(L"Couldn't find mapping for 0x%08llx 0x%08llx", vaddr, size); + return -1; + } + //- + + if (auto it = m_objects.find(itItem->second.heapAddr); it != m_objects.end()) { + vmaVirtualFree(it->second.vmaBlock, itItem->second.vmaAlloc); + } else { + LOG_ERR(L"Couldn't find Heap for 0x%08llx", itItem->second.heapAddr); + } + + LOG_DEBUG(L"unmap| 0x%08llx 0x%08llx", vaddr, size); + m_usedSize -= itItem->second.size; + + m_mappings.erase(itItem); + return Ok; +} + +uint64_t DirectMemory::size() const { + return SCE_KERNEL_MAIN_DMEM_SIZE; +} + +int DirectMemory::getAvailableSize(uint32_t start, uint32_t end, size_t alignment, uint32_t* startOut, size_t* sizeOut) const { + LOG_USE_MODULE(DirectMemory); + LOG_DEBUG(L"availableSize: start:0x%lx end:0x%lx alignment:0x%08llx", start, end, alignment); + + auto itItem = m_objects.lower_bound(DIRECTMEM_START + start); + if (m_objects.empty() || itItem == m_objects.end()) { + *startOut = start; + *sizeOut = std::min(SCE_KERNEL_MAIN_DMEM_SIZE, (uint64_t)end - start); + return Ok; + } + + // *startOut = start; + // *sizeOut = (itItem->second.addr - DIRECTMEM_START) - start; + if (itItem->second.addr + itItem->second.size >= DIRECTMEM_START + end) { + *startOut = end; + *sizeOut = 0; + return Ok; + } + + *startOut = start; + *sizeOut = 0; + + auto itEnd = m_objects.lower_bound(DIRECTMEM_START + end); + for (; itItem != itEnd; ++itItem) { + *startOut = (itItem->second.addr + itItem->second.size) - DIRECTMEM_START; + } + + if (*startOut > end) + *sizeOut = *startOut - end; + else + *sizeOut = end - *startOut; + + return Ok; +} + +void DirectMemory::deinit() { + for (auto& item: m_objects) { + if (checkIsGPU(item.second.prot)) { + // done by gpuMemoryManager + } else { + // memory::free(item.first); + } + } + m_objects.clear(); +} + +int32_t DirectMemory::virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const { + LOG_USE_MODULE(DirectMemory); + + boost::unique_lock lock(m_mutexInt); + if (m_objects.empty()) return getErr(ErrCode::_EACCES); + + auto itItem = m_objects.lower_bound(addr); + if (itItem == m_objects.end() && addr > (itItem->first + itItem->second.size)) return getErr(ErrCode::_EACCES); // End reached + + if (itItem == m_objects.end() || (itItem != m_objects.begin() && itItem->first != addr)) --itItem; // Get the correct item + + info->protection = itItem->second.prot; + info->memoryType = itItem->second.memoryType; + info->isFlexibleMemory = false; + info->isDirectMemory = true; + info->isPooledMemory = false; + // info->isStack = false; // done by parent + + auto itMapping = m_mappings.lower_bound(itItem->first); + if (itMapping == m_mappings.end() || (itMapping != m_mappings.begin() && itMapping->first != itItem->first)) --itMapping; // Get the correct item + + if (!(itMapping->first <= itItem->first && (itMapping->first + itMapping->second.size > itItem->first))) { + if (itItem->second.state == MemoryState::Reserved) { + info->start = (void*)itItem->first; + info->end = (void*)(itItem->first + itItem->second.size); + info->offset = 0; + info->isCommitted = false; + return Ok; + } + return getErr(ErrCode::_EACCES); + } + + info->start = (void*)itMapping->first; + info->end = (void*)(itMapping->first + itMapping->second.size); + info->offset = 0; + + info->isCommitted = true; + return Ok; +} + +int32_t DirectMemory::directQuery(uint64_t offset, SceKernelDirectMemoryQueryInfo* info) const { + LOG_USE_MODULE(DirectMemory); + + boost::unique_lock lock(m_mutexInt); + + for (auto& item: m_objects) { + auto off = item.second.addr - DIRECTMEM_START; + if (offset >= off && offset < off + item.second.size) { + info->start = item.second.addr; + info->end = item.second.addr + item.second.size; + + info->memoryType = item.second.memoryType; + + LOG_DEBUG(L"Query OK: offset:0x%08llx -> start:0x%08llx end:0x%08llx type:%d", offset, info->start, info->end, info->memoryType); + return Ok; + } + } + + LOG_DEBUG(L"Query Error: offset:0x%08llx", offset); + + return getErr(ErrCode::_EACCES); +} diff --git a/core/dmem/types/flexiblemem.cpp b/core/dmem/types/flexiblemem.cpp new file mode 100644 index 00000000..f2a1ea3f --- /dev/null +++ b/core/dmem/types/flexiblemem.cpp @@ -0,0 +1,267 @@ +#include "../memoryManager.h" +#include "core/kernel/filesystem.h" +#include "core/runtime/procParam.h" +#include "core/runtime/runtimeLinker.h" +#include "core/videoout/videoout.h" +#include "logging.h" +#include "memory.h" +#include "modules/libkernel/codes.h" +#include "modules/libkernel/dmem.h" +#include "utility/utility.h" + +#include +#include +#include +#include +#include + +#undef min +LOG_DEFINE_MODULE(FlexibleMemory); + +namespace { +constexpr uint64_t DIRECTMEM_START = 0x100000000u; + +enum class MemoryState { + Free, + Reserved, + Commited, +}; + +struct MemoryMapping { + uint64_t addr = 0; + uint64_t size = 0; + int prot = 0; +}; + +bool checkIsGPU(int protection) { + return (protection & 0xf0) > 0; +} + +DWORD convProtection(int prot) { + switch (prot & 0xf) { + case 0: return PAGE_NOACCESS; + case 1: return PAGE_READONLY; + case 2: + case 3: return PAGE_READWRITE; + case 4: return PAGE_EXECUTE; + case 5: return PAGE_EXECUTE_READ; + case 6: + case 7: return PAGE_EXECUTE_READWRITE; + } + + if (checkIsGPU(prot)) { + return PAGE_READWRITE; // special case: cpu read/writes gpumemory on host memory + } + + return PAGE_NOACCESS; +} +} // namespace + +class FlexibleMemory: public IMemoryType { + mutable boost::mutex m_mutexInt; + + IMemoryManager* m_parent; + + std::map m_mappings; + + uint64_t m_configuresSize = 448 * 1024 * 1024; + + uint64_t m_usedSize = 0; + + public: + FlexibleMemory(IMemoryManager* parent): m_parent(parent) {} + + virtual ~FlexibleMemory() { deinit(); } + + // ### Interface + + void setTotalSize(uint64_t totalSize) final; + + int alloc(size_t len, size_t alignment, int memoryType, uint64_t* outAddr) final; + int free(off_t start, size_t len) final; + + int map(uint64_t vaddr, off_t directMemoryOffset, size_t len, int prot, int flags, size_t alignment, uint64_t* outAddr) final; + int unmap(uint64_t vaddr, uint64_t size) final; + + virtual int reserve(uint64_t start, size_t len, size_t alignment, int flags, uint64_t* outAddr) final; + + uint64_t size() const final; + + int getAvailableSize(uint32_t start, uint32_t end, size_t alignment, uint32_t* startOut, size_t* sizeOut) const final; + + int32_t virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const final; + + int32_t directQuery(uint64_t offset, SceKernelDirectMemoryQueryInfo* info) const final; + + void deinit() final; +}; + +std::unique_ptr createFlexibleMemory(IMemoryManager* parent) { + return std::make_unique(parent); +} + +void FlexibleMemory::setTotalSize(uint64_t totalSize) { + LOG_USE_MODULE(FlexibleMemory); + + m_configuresSize = totalSize; +} + +int FlexibleMemory::alloc(size_t len, size_t alignment, int memoryType, uint64_t* outAddr) { + LOG_USE_MODULE(FlexibleMemory); + + LOG_CRIT(L"NOT IMPLEMENTED"); + return Ok; +} + +int FlexibleMemory::free(off_t start, size_t len) { + LOG_USE_MODULE(FlexibleMemory); + + LOG_CRIT(L"NOT IMPLEMENTED"); + return Ok; +} + +int FlexibleMemory::map(uint64_t vaddr, off_t offset, size_t len, int prot, int flags, size_t alignment, uint64_t* outAddr) { + LOG_USE_MODULE(FlexibleMemory); + + boost::unique_lock lock(m_mutexInt); + if (flags & (int)filesystem::SceMapMode::VOID_) { + LOG_CRIT(L"todo void map"); + } + + uint64_t desVaddr = 0; + if (flags & (int)filesystem::SceMapMode::FIXED) { + desVaddr = vaddr; + } + + if (m_configuresSize <= (m_usedSize + len)) return getErr(ErrCode::_ENOMEM); + + // Commit + { + MEM_ADDRESS_REQUIREMENTS addressReqs = {0}; + MEM_EXTENDED_PARAMETER extendedParams = {0}; + + addressReqs.Alignment = 0; // 64 KB alignment + addressReqs.LowestStartingAddress = (PVOID)DIRECTMEM_START; + addressReqs.HighestEndingAddress = (PVOID)0; + + extendedParams.Type = MemExtendedParameterAddressRequirements; + extendedParams.Pointer = &addressReqs; + + *outAddr = (uint64_t)VirtualAlloc2(NULL, (void*)desVaddr, len, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, convProtection(prot), + desVaddr != 0 ? 0 : &extendedParams, desVaddr != 0 ? 0 : 1); + if (*outAddr == 0) { + auto const err = GetLastError(); + LOG_ERR(L"Commit Error| addr:0x%08llx len:0x%08llx err:%d", desVaddr, len, GetLastError()); + return getErr(ErrCode::_EINVAL); + } + } + // - commit + + m_mappings.emplace(std::make_pair(*outAddr, MemoryMapping {.addr = *outAddr, .size = len, .prot = prot})); + + m_parent->registerMapping(*outAddr, len, MappingType::Flexible); + m_usedSize += len; + + LOG_DEBUG(L"-> Map: start:0x%08llx(0x%x) len:0x%08llx alignment:0x%08llx prot:%d -> 0x%08llx", vaddr, offset, len, alignment, prot, *outAddr); + + if (checkIsGPU(prot)) { + if (!accessVideoOut().notify_allocHeap(*outAddr, len, prot)) { + return getErr(ErrCode::_EINVAL); + } + } + + return Ok; +} + +int FlexibleMemory::reserve(uint64_t start, size_t len, size_t alignment, int flags, uint64_t* outAddr) { + LOG_USE_MODULE(FlexibleMemory); + + LOG_CRIT(L"NOT IMPLEMENTED"); + return Ok; +} + +int FlexibleMemory::unmap(uint64_t vaddr, uint64_t size) { + LOG_USE_MODULE(FlexibleMemory); + + boost::unique_lock lock(m_mutexInt); + + // Find the object + if (m_mappings.empty()) return getErr(ErrCode::_EINVAL); + + auto itItem = m_mappings.lower_bound(vaddr); + if (itItem == m_mappings.end() || (itItem != m_mappings.begin() && itItem->first != vaddr)) --itItem; // Get the correct item + + if (!(itItem->first <= vaddr && (itItem->first + itItem->second.size >= vaddr))) { + LOG_ERR(L"Couldn't find mapping for 0x%08llx 0x%08llx", vaddr, size); + return -1; + } + //- + + if (checkIsGPU(itItem->second.prot)) { + // todo + + } else { + if (itItem->second.addr != 0) VirtualFree((void*)itItem->second.addr, itItem->second.size, 0); + } + + LOG_DEBUG(L"unmap| 0x%08llx 0x%08llx", vaddr, size); + m_usedSize -= itItem->second.size; + + m_mappings.erase(itItem); + return Ok; +} + +uint64_t FlexibleMemory::size() const { + return m_configuresSize; +} + +int FlexibleMemory::getAvailableSize(uint32_t start, uint32_t end, size_t alignment, uint32_t* startOut, size_t* sizeOut) const { + LOG_USE_MODULE(FlexibleMemory); + + *sizeOut = m_configuresSize - m_usedSize; + LOG_DEBUG(L"availableSize: 0x%08llx ", *sizeOut); + + return Ok; +} + +void FlexibleMemory::deinit() { + for (auto& item: m_mappings) { + if (checkIsGPU(item.second.prot)) { + // done by gpuMemoryManager + } else { + // memory::free(item.first); + } + } + m_mappings.clear(); +} + +int32_t FlexibleMemory::virtualQuery(uint64_t addr, SceKernelVirtualQueryInfo* info) const { + boost::unique_lock lock(m_mutexInt); + + auto itMapping = m_mappings.lower_bound(addr); + if (itMapping == m_mappings.end() || (itMapping != m_mappings.begin() && itMapping->first != addr)) --itMapping; // Get the correct item + + if (!(itMapping->first <= addr && (itMapping->first + itMapping->second.size >= addr))) { + return getErr(ErrCode::_EACCES); + } + + info->protection = itMapping->second.prot; + info->memoryType = 3; + info->isFlexibleMemory = true; + info->isDirectMemory = false; + info->isPooledMemory = false; + // info->isStack = false; // done by parent + + info->start = (void*)itMapping->first; + info->end = (void*)(itMapping->first + itMapping->second.size); + info->offset = 0; + + info->isCommitted = true; + return Ok; +} + +int32_t FlexibleMemory::directQuery(uint64_t offset, SceKernelDirectMemoryQueryInfo* info) const { + LOG_USE_MODULE(FlexibleMemory); + LOG_CRIT(L"NOT IMPLEMENTED"); + return Ok; +} \ No newline at end of file diff --git a/core/dmem/types/memory.h b/core/dmem/types/memory.h new file mode 100644 index 00000000..26c56563 --- /dev/null +++ b/core/dmem/types/memory.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../imemory.h" + +class IMemoryManager; +std::unique_ptr createDirectMemory(IMemoryManager* parent); +std::unique_ptr createFlexibleMemory(IMemoryManager* parent); \ No newline at end of file diff --git a/core/fileManager/CMakeLists.txt b/core/fileManager/CMakeLists.txt new file mode 100644 index 00000000..fd90814d --- /dev/null +++ b/core/fileManager/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(fileManager OBJECT + fileManager.cpp + types/type_in.cpp + types/type_out.cpp + types/type_zero.cpp + types/type_null.cpp + types/type_file.cpp + types/type_random.cpp + types/type_rng.cpp + types/type_lib.cpp +) + +add_dependencies(fileManager third_party psOff_utility) + +file(COPY fileManager.h ifile.h DESTINATION ${DST_INCLUDE_DIR}/fileManager) \ No newline at end of file diff --git a/core/fileManager/fileManager.cpp b/core/fileManager/fileManager.cpp new file mode 100644 index 00000000..268ea213 --- /dev/null +++ b/core/fileManager/fileManager.cpp @@ -0,0 +1,273 @@ +#define __APICALL_EXTERN +#include "fileManager.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "magic_enum/magic_enum.hpp" +#include "types/type_in.h" +#include "types/type_out.h" +#include "utility/utility.h" + +#include +#include +#include +#include +#include +#include + +LOG_DEFINE_MODULE(FileManager); + +struct UniData { + CLASS_NO_COPY(UniData); + + enum class Type { File, Dir }; + Type const m_type; + std::filesystem::path const m_path; + + UniData(Type type, std::filesystem::path const& path): m_type(type), m_path(path) {} + + virtual ~UniData() = default; +}; + +struct FileData: public UniData { + std::unique_ptr const m_file; + + FileData(std::unique_ptr&& file, std::filesystem::path const& path): UniData(UniData::Type::File, path), m_file(std::move(file)) {} + + virtual ~FileData() { m_file->sync(); } +}; + +struct DirData: public UniData { + std::unique_ptr const m_file; + uint32_t count = 0; + + DirData(std::unique_ptr&& dirIt, std::filesystem::path const& path) + : UniData(UniData::Type::Dir, path), m_file(std::move(dirIt)) {} + + virtual ~DirData() = default; +}; + +namespace { + +int insertItem(std::vector>& container, UniData* item) { + int index = -1; + for (size_t n = 0; n < container.size(); ++n) { + if (!container[n]) { + container[n] = std::unique_ptr(item); + index = n; + break; + } + } + + if (index < 0) { + index = container.size(); + container.push_back(std::unique_ptr(item)); + } + return index; +} + +} // namespace + +class FileManager: public IFileManager { + std::vector> m_openFiles; + std::mutex m_mutext_int; + + /// First: mountpoint Second: RootDir + std::unordered_map> m_mountPointList; + std::filesystem::path m_dirGameFiles; + std::unordered_map m_mappedPaths; + + bool m_updateSearch = false; + + public: + FileManager() { init(); }; + + virtual ~FileManager() = default; + + void init() { + // Order of the first three files is important, do not change it! + assert(addFile(createType_in(), "/dev/stdin") == 0); + assert(addFile(createType_out(SCE_TYPEOUT_ERROR), "/dev/stdout") == 1); + assert(addFile(createType_out(SCE_TYPEOUT_DEBUG), "/dev/stderr") == 2); + } + + void addMountPoint(std::string_view mountPoint, std::filesystem::path const& root, MountType type) final { + LOG_USE_MODULE(FileManager); + std::unique_lock const lock(m_mutext_int); + m_mountPointList[type][std::string(mountPoint)] = root; + + // Create root dir + std::filesystem::create_directories(root); + LOG_INFO(L"+ MountPoint: %S->%s", mountPoint.data(), root.c_str()); + } + + std::filesystem::path getMountPoint(MountType type, std::string mountPoint) final { + std::unique_lock const lock(m_mutext_int); + if (auto it = m_mountPointList[type].find(mountPoint); it != m_mountPointList[type].end()) return it->second; + return {}; + } + + void clearMountPoint(MountType type, std::string mountPoint) final { + LOG_USE_MODULE(FileManager); + std::unique_lock const lock(m_mutext_int); + LOG_INFO(L"- MountPoint for %S", magic_enum::enum_name(type).data()); + m_mountPointList[type].erase(mountPoint); + } + + std::optional getMappedPath(std::string_view path) final { + LOG_USE_MODULE(FileManager); + std::unique_lock const lock(m_mutext_int); + + // Check Cache + { + auto it = m_mappedPaths.find(path.data()); + if (it != m_mappedPaths.end()) { + return it->second; + } + } + //- + + // Special case: Mounted Gamefiles + if (path[0] != '/') { + auto const mapped = m_dirGameFiles / path; + LOG_INFO(L"Gamefiles mapped:%s", mapped.c_str()); + return mapped; + } + // - + + uint8_t offset = (path[0] == '/' && path[1] == '/') ? 1 : 0; // Path can start with //app0 ? + + for (auto const& mountType: m_mountPointList) { + for (auto const& item: mountType.second) { + auto const& mountPoint = item.first; + auto const& rootDir = item.second; + + if (path.size() >= mountPoint.size() && std::mismatch(mountPoint.begin(), mountPoint.end(), path.begin() + offset).first == mountPoint.end()) { + if (path[mountPoint.size() + offset] == '/') ++offset; // path.substr() should return relative path + + if (mountType.first == MountType::App && m_updateSearch) { + auto const& updateDir = m_mountPointList[MountType::Update].begin()->second; + auto const mapped = updateDir / path.substr(mountPoint.size() + offset); + if (std::filesystem::exists(mapped)) { + LOG_DEBUG(L"mapped: %s root:%s source:%S", mapped.c_str(), updateDir.c_str(), path.data()); + m_mappedPaths[path.data()] = mapped; + return mapped; + } + } + + auto const mapped = rootDir / path.substr(mountPoint.size() + offset); + LOG_DEBUG(L"mapped: %s root:%s source:%S", mapped.c_str(), rootDir.c_str(), path.data()); + m_mappedPaths[path.data()] = mapped; + return mapped; + } + } + } + + LOG_WARN(L"Unknown map:%S", path.data()); + return {}; + } + + void setGameFilesDir(std::filesystem::path const& path) final { + std::unique_lock const lock(m_mutext_int); + m_dirGameFiles = path; + } + + std::filesystem::path const& getGameFilesDir() const final { return m_dirGameFiles; } + + int addFile(std::unique_ptr&& file, std::filesystem::path const& path) final { + std::unique_lock const lock(m_mutext_int); + + return insertItem(m_openFiles, std::make_unique(std::move(file), path).release()); + } + + int addDirIterator(std::unique_ptr&& it, std::filesystem::path const& path) final { + std::unique_lock const lock(m_mutext_int); + + return insertItem(m_openFiles, std::make_unique(std::move(it), path).release()); + } + + void remove(int handle) final { + std::unique_lock const lock(m_mutext_int); + m_openFiles[handle].reset(); + } + + IFile* accessFile(int handle) final { + std::unique_lock const lock(m_mutext_int); + if (handle < m_openFiles.size() && m_openFiles[handle] && m_openFiles[handle]->m_type == UniData::Type::File) { + return static_cast(m_openFiles[handle].get())->m_file.get(); + } + return nullptr; + } + + std::filesystem::path getPath(int handle) final { + std::unique_lock const lock(m_mutext_int); + if (handle < m_openFiles.size() && m_openFiles[handle]) { + return m_openFiles[handle]->m_path; + } + return {}; + } + + int getDents(int handle, char* buf, int nbytes, int64_t* basep) final { + LOG_USE_MODULE(FileManager); + + std::unique_lock const lock(m_mutext_int); + + auto& oFile = m_openFiles[handle]; + if (oFile->m_type != UniData::Type::Dir) return -1; + + auto dir = static_cast(oFile.get()); + auto endDir = std::filesystem::directory_iterator(); + + if ((*dir->m_file) == endDir) return 0; + +#pragma pack(push, 1) + + struct DataStruct { + uint32_t fileno; + uint16_t reclen; + uint8_t type; + uint8_t namlen; + char name[256]; + }; + +#pragma pack(pop, 1) + + auto count = dir->count; + int n = 0; + + while (n < nbytes - sizeof(DataStruct)) { + auto item = (DataStruct*)(buf + n); + + auto const filename = (*dir->m_file)->path().filename().string(); + if (sizeof(DataStruct) + std::min(filename.size(), 255llu) >= nbytes) break; + + item->fileno = count; + item->type = ((*dir->m_file)->is_regular_file() ? 8 : 4); + item->namlen = filename.copy(item->name, 255); + item->name[item->namlen] = '\0'; + + n += sizeof(DataStruct); + item->reclen = sizeof(DataStruct); + + LOG_DEBUG(L"KernelGetdirentries[%d]: %S %u offset:%u count:%u", handle, item->name, item->type, n, count); + + std::error_code err; + (*dir->m_file).increment(err); + count = ++dir->count; + + if ((*dir->m_file) == endDir || err) break; + }; + + if (basep != nullptr) { + *basep = count; + } + return n; + } + + void enableUpdateSearch() final { m_updateSearch = true; } +}; + +IFileManager& accessFileManager() { + static FileManager inst; + return inst; +} diff --git a/core/fileManager/fileManager.h b/core/fileManager/fileManager.h new file mode 100644 index 00000000..647173f7 --- /dev/null +++ b/core/fileManager/fileManager.h @@ -0,0 +1,133 @@ +#pragma once +#include "ifile.h" +#include "utility/utility.h" + +#include +#include +#include +#include +#include + +enum class MountType { App, Update, Save, Temp, Data }; +constexpr int FILE_DESCRIPTOR_MIN = 3; + +constexpr std::string_view MOUNT_POINT_TEMP = "temp"; +constexpr std::string_view MOUNT_POINT_DATA = "data"; +constexpr std::string_view MOUNT_POINT_APP = "/app0/"; +constexpr std::string_view MOUNT_POINT_UPDATE = "/app1/"; +constexpr std::string_view SAVE_DATA_POINT = "/savedata"; + +class IFileManager { + CLASS_NO_COPY(IFileManager); + CLASS_NO_MOVE(IFileManager); + + protected: + IFileManager() = default; + virtual ~IFileManager() = default; + + public: + /** + * @brief Get the mapped path. (Resolves linux paths, mounts etc.) + * + * @param path + * @return std::optional on success, the mapped path + */ + virtual std::optional getMappedPath(std::string_view path) = 0; + + /** + * @brief Adds a fake mount point. getMappedPath() uses those to resolve to the correct path. + * Just replaces both strings + * + * @param mountPoint e.g. /savedata + * @param root actual path e.g. C:/savedir + * @param type Used to group the mounts. normaly only one mount per type is used. (savedata uses multiple ...) + */ + virtual void addMountPoint(std::string_view mountPoint, std::filesystem::path const& root, MountType type) = 0; + + /** + * @brief Get the actual path for a mount point + * + * @param type + * @param mountPoint e.g. /savedata + * @return std::filesystem::path empty on error + */ + virtual std::filesystem::path getMountPoint(MountType type, std::string mountPoint) = 0; + + /** + * @brief Removes the mountpoint + * + * @param type + * @param mountPoint + */ + virtual void clearMountPoint(MountType type, std::string mountPoint) = 0; + + /** + * @brief Sets the path where all games files should be located (savegames etc). + * Used by main to setup the emulator + * @param path + */ + virtual void setGameFilesDir(std::filesystem::path const& path) = 0; + + /** + * @brief Get the Game Files Dir + * + * @return std::filesystem::path const& + */ + virtual std::filesystem::path const& getGameFilesDir() const = 0; + + /** + * @brief Add a open file. + * + * @param file + * @param path the mapped path to the file/folder + * @return int handle of the fstream. used by getFile() etc. + */ + virtual int addFile(std::unique_ptr&& file, std::filesystem::path const& path) = 0; + + /** + * @brief Add a directory_iterator + * + * @param it + * @param path the mapped path to the folder + * @return int handle to the directory_iterator + */ + virtual int addDirIterator(std::unique_ptr&& it, std::filesystem::path const& path) = 0; + + /** + * @brief remove the file/folder with the associated handle. + * Only the internal mapping is removed! + * + * @param handle + */ + virtual void remove(int handle) = 0; + + /** + * @brief Get access to the File + * + * @param handle + * @return std::fstream* + */ + virtual IFile* accessFile(int handle) = 0; + + virtual int getDents(int handle, char* buf, int nbytes, int64_t* basep) = 0; + + /** + * @brief Get the mapped path of a open file/folder + * + * @param handle + * @return std::filesystem::path + */ + virtual std::filesystem::path getPath(int handle) = 0; + + virtual void enableUpdateSearch() = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif +__APICALL IFileManager& accessFileManager(); +#undef __APICALL \ No newline at end of file diff --git a/core/fileManager/ifile.h b/core/fileManager/ifile.h new file mode 100644 index 00000000..1c71ea8a --- /dev/null +++ b/core/fileManager/ifile.h @@ -0,0 +1,87 @@ +#pragma once + +#include "modules_include/common.h" +#include "utility/utility.h" + +#include + +enum class FileType { + File, + Device, + Library, +}; + +enum class SceWhence : int { + beg, + cur, + end, +}; + +class IFile { + CLASS_NO_COPY(IFile); + CLASS_NO_MOVE(IFile); + FileType const m_type; + + protected: + int32_t m_errCode = 0; + + IFile(FileType type): m_type(type) {} + + public: + virtual ~IFile() = default; + + auto getType() const { return m_type; } + + auto getErr() const { return m_errCode; } + + void clearError() { m_errCode = 0; } + + /** + * @brief read n bytes from file to (uint8_t)buf[nbytes] + * + * @param buf + * @param nbytes + * @return size_t number of bytes read, 0:empty + */ + virtual size_t read(void* buf, size_t nbytes) = 0; + + /** + * @brief write n bytes from (uint8_t)buf[nbytes] to file + * + * @param buf + * @param nbytes + * @return size_t number of bytes written + */ + virtual size_t write(void* buf, size_t nbytes) = 0; + + virtual void sync() = 0; + + /** + * @brief Set the read/write position + * + * @param offset + * @param whence + * @return int64_t + */ + virtual int64_t lseek(int64_t offset, SceWhence whence) = 0; + + /** + * @brief Manipulates the underlying device parameters of special files. + * + * @param handle + * @param request + * @return int error code, 0:no error + */ + virtual int ioctl(int request, SceVariadicList argp) = 0; + + /** + * @brief Performs one of the operations on the open file descriptor. + * + * @param handle + * @param cmd + * @return int error code, 0:no error + */ + virtual int fcntl(int cmd, SceVariadicList argp) = 0; + + virtual void* getNative() = 0; +}; diff --git a/core/fileManager/readme.md b/core/fileManager/readme.md new file mode 100644 index 00000000..2171e570 --- /dev/null +++ b/core/fileManager/readme.md @@ -0,0 +1,8 @@ +## FileManager + +Manages all Linux files (mounts etc.), converts paths to Host paths and is the owner of opened files. + +
+ +![](../../out/docs/uml/modules/fileManager.svg) +
diff --git a/core/fileManager/types/type_file.cpp b/core/fileManager/types/type_file.cpp new file mode 100644 index 00000000..55229cc5 --- /dev/null +++ b/core/fileManager/types/type_file.cpp @@ -0,0 +1,157 @@ +#include "type_file.h" + +#include "logging.h" + +#include +#include +#include + +LOG_DEFINE_MODULE(File); + +namespace { +typedef HANDLE Handle_t; + +std::pair open(std::filesystem::path path, filesystem::SceOpen mode) { + LOG_USE_MODULE(File); + + DWORD shareMode = 0; + DWORD access = 0; + + // Set acces and shareMode; + switch (mode.mode) { + case filesystem::SceOpenMode::RDONLY: { + access = GENERIC_READ; + if (!mode.exlock) shareMode = FILE_SHARE_READ; + } break; + case filesystem::SceOpenMode::WRONLY: { + access = GENERIC_WRITE; + if (!mode.exlock) shareMode = FILE_SHARE_WRITE; + } break; + case filesystem::SceOpenMode::RDWR: { + access = GENERIC_READ | GENERIC_WRITE; + if (!mode.exlock) shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + } break; + } + // - + + DWORD flags = OPEN_EXISTING; + if (mode.trunc) flags = CREATE_ALWAYS; + // Create new if it doesn't exist + else if (mode.create) { + flags = CREATE_NEW; + } + + auto pathString = path.wstring(); + std::replace(pathString.begin(), pathString.end(), L'/', L'\\'); + HANDLE file = CreateFileW(pathString.c_str(), access, shareMode, 0, flags, FILE_ATTRIBUTE_NORMAL, 0); + if (file == INVALID_HANDLE_VALUE) { + auto const lastErr = GetLastError(); + LOG_WARN(L"open file: %s flags:0x%x access:0x%x shareMode:0x%x mode:0x%llx(%u) error:0x%x ", pathString.c_str(), flags, access, shareMode, + *(uint32_t*)(&mode), mode.mode, lastErr); + if (lastErr == 0x57) return {nullptr, (int)ErrCode::_EINVAL}; + if (lastErr == 0x20) return {nullptr, (int)ErrCode::_EALREADY}; + return {nullptr, Ok}; + } + + LOG_DEBUG(L"[%lld] opened file: %s flags:0x%x access:0x%x shareMode:0x%x mode:0x%x(%u)", file, pathString.c_str(), flags, access, shareMode, + *(uint32_t*)(&mode), mode.mode); + return {file, Ok}; +} +} // namespace + +class TypeFile: public IFile { + Handle_t m_file = INVALID_HANDLE_VALUE; + + filesystem::SceOpen const m_mode; + + bool m_isAppend = false; + + public: + TypeFile(std::filesystem::path path, filesystem::SceOpen mode): IFile(FileType::File), m_mode(mode) { + auto [file, err] = open(path, mode); + m_file = file; + + m_errCode = err; + + m_isAppend = mode.append; + } + + virtual ~TypeFile() { + if (m_file != INVALID_HANDLE_VALUE) { + CloseHandle(m_file); + sync(); + } + } + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + virtual void* getNative() final { return m_file; } +}; + +std::unique_ptr createType_file(std::filesystem::path path, filesystem::SceOpen mode) { + return std::make_unique(path, mode); +} + +size_t TypeFile::read(void* buf, size_t nbytes) { + DWORD numRead = 0; + if (!ReadFile(m_file, buf, nbytes, &numRead, nullptr)) { + auto const lastError = GetLastError(); + if (lastError != ERROR_IO_PENDING) { + LOG_USE_MODULE(File); + LOG_WARN(L"Read error: 0x%x", GetLastError()); + m_errCode = (int)ErrCode::_EIO; + + return 0; + } + } + return numRead; +} + +size_t TypeFile::write(void* buf, size_t nbytes) { + // The file is opened in append mode. Before each write(2), + // the file offset is positioned at the end of the file + if (m_isAppend) { + lseek(0, SceWhence::end); + } + // - + + DWORD numWrite = 0; + if (!WriteFile(m_file, buf, nbytes, &numWrite, nullptr)) { + LOG_USE_MODULE(File); + LOG_WARN(L"Write error: 0x%x", GetLastError()); + m_errCode = (int)ErrCode::_EIO; + return 0; + } + return numWrite; +} + +void TypeFile::sync() { + FlushFileBuffers(m_file); +} + +int TypeFile::ioctl(int request, SceVariadicList argp) { + return 0; +} + +int TypeFile::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeFile::lseek(int64_t offset, SceWhence whence) { + static int _whence[] = { + FILE_BEGIN, + FILE_CURRENT, + FILE_END, + }; + + LONG upperHalf = offset >> 32; + DWORD lowerHalf = SetFilePointer(m_file, (uint32_t)offset, &upperHalf, _whence[(int)whence]); + + return ((uint64_t)upperHalf << 32) | lowerHalf; +} diff --git a/core/fileManager/types/type_file.h b/core/fileManager/types/type_file.h new file mode 100644 index 00000000..b57f1143 --- /dev/null +++ b/core/fileManager/types/type_file.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../ifile.h" +#include "core/kernel/filesystem.h" + +#include +#include + +std::unique_ptr createType_file(std::filesystem::path path, filesystem::SceOpen mode); \ No newline at end of file diff --git a/core/fileManager/types/type_in.cpp b/core/fileManager/types/type_in.cpp new file mode 100644 index 00000000..c345c198 --- /dev/null +++ b/core/fileManager/types/type_in.cpp @@ -0,0 +1,48 @@ +#include "type_in.h" + +#include "modules_include/common.h" + +class TypeIn: public IFile { + public: + TypeIn(): IFile(FileType::Device) {} + + virtual ~TypeIn() {} + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_in() { + return std::make_unique(); +} + +size_t TypeIn::read(void* buf, size_t nbytes) { + if (nbytes == 0) return 0; + printf("Emulator awaits your input: "); + return std::fread(buf, 1, nbytes, stdin); +} + +size_t TypeIn::write(void* buf, size_t nbytes) { + return nbytes; +} + +void TypeIn::sync() {} + +int TypeIn::ioctl(int request, SceVariadicList argp) { + return 0; +} + +int TypeIn::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeIn::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_in.h b/core/fileManager/types/type_in.h new file mode 100644 index 00000000..7794eba4 --- /dev/null +++ b/core/fileManager/types/type_in.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../ifile.h" + +#include + +std::unique_ptr createType_in(); diff --git a/core/fileManager/types/type_lib.cpp b/core/fileManager/types/type_lib.cpp new file mode 100644 index 00000000..f5720e22 --- /dev/null +++ b/core/fileManager/types/type_lib.cpp @@ -0,0 +1,55 @@ +#include "type_lib.h" + +#include "core/runtime/runtimeLinker.h" +#include "logging.h" + +LOG_DEFINE_MODULE(IODevice_LIB); + +class TypeLib: public IFile { + Program* m_prog; + + public: + TypeLib(Program* prog): IFile(FileType::Library), m_prog(prog) {} + + ~TypeLib() { accessRuntimeLinker().stopModule(m_prog->id); } + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_lib(Program* prog) { + return std::make_unique(prog); +} + +size_t TypeLib::read(void* buf, size_t nbytes) { + LOG_USE_MODULE(IODevice_LIB); + LOG_CRIT(L"Program(%p)->read(%p, %llu)", m_prog->id, buf, nbytes); + return 0; +} + +size_t TypeLib::write(void* buf, size_t nbytes) { + LOG_USE_MODULE(IODevice_LIB); + LOG_CRIT(L"Program(%p)->write(%p, %llu)", m_prog->id, buf, nbytes); + return 0; +} + +void TypeLib::sync() {} + +int TypeLib::ioctl(int request, SceVariadicList argp) { + return 0; +} + +int TypeLib::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeLib::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_lib.h b/core/fileManager/types/type_lib.h new file mode 100644 index 00000000..8a2d381f --- /dev/null +++ b/core/fileManager/types/type_lib.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../ifile.h" + +struct Program; +std::unique_ptr createType_lib(Program* prog); diff --git a/core/fileManager/types/type_null.cpp b/core/fileManager/types/type_null.cpp new file mode 100644 index 00000000..fbfb6137 --- /dev/null +++ b/core/fileManager/types/type_null.cpp @@ -0,0 +1,46 @@ +#include "type_null.h" + +#include "modules_include/common.h" + +class TypeNull: public IFile { + public: + TypeNull(): IFile(FileType::Device) {} + + virtual ~TypeNull() {} + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_null() { + return std::make_unique(); +} + +size_t TypeNull::read(void* buf, size_t nbytes) { + return -1; +} + +size_t TypeNull::write(void* buf, size_t nbytes) { + return nbytes; +} + +void TypeNull::sync() {} + +int TypeNull::ioctl(int request, SceVariadicList argp) { + return -1; +} + +int TypeNull::fcntl(int cmd, SceVariadicList argp) { + return -1; +} + +int64_t TypeNull::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_null.h b/core/fileManager/types/type_null.h new file mode 100644 index 00000000..3747d8b0 --- /dev/null +++ b/core/fileManager/types/type_null.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../ifile.h" + +#include + +std::unique_ptr createType_null(); diff --git a/core/fileManager/types/type_out.cpp b/core/fileManager/types/type_out.cpp new file mode 100644 index 00000000..cec13ce7 --- /dev/null +++ b/core/fileManager/types/type_out.cpp @@ -0,0 +1,70 @@ +#include "type_out.h" + +#include "logging.h" + +LOG_DEFINE_MODULE(IODevice_OUT); + +class TypeOut: public IFile { + const SceFileOutChannel m_channel = SCE_TYPEOUT_ERROR; + + public: + TypeOut(SceFileOutChannel ch): IFile(FileType::Device), m_channel(ch) {} + + virtual ~TypeOut() {} + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_out(SceFileOutChannel ch) { + return std::make_unique(ch); +} + +size_t TypeOut::read(void* buf, size_t nbytes) { + return 0; +} + +size_t TypeOut::write(void* buf, size_t nbytes) { + LOG_USE_MODULE(IODevice_OUT); + + std::string str((const char*)buf, nbytes); + while (str.back() == '\n') + str.pop_back(); + + if (str.length() == 0) return nbytes; + + switch (m_channel) { + case SCE_TYPEOUT_ERROR: { + printf("Console:%s\n", str.c_str()); + LOG_DEBUG(L"%S", str.c_str()); + + } break; + + case SCE_TYPEOUT_DEBUG: LOG_DEBUG(L"%S", str.c_str()); break; + + default: LOG_CRIT(L"Unknown channel: %d", (uint32_t)m_channel); break; + } + + return nbytes; +} + +void TypeOut::sync() {} + +int TypeOut::ioctl(int request, SceVariadicList argp) { + return 0; +} + +int TypeOut::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeOut::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_out.h b/core/fileManager/types/type_out.h new file mode 100644 index 00000000..29f3a717 --- /dev/null +++ b/core/fileManager/types/type_out.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../ifile.h" + +#include + +enum SceFileOutChannel { + SCE_TYPEOUT_ERROR, + SCE_TYPEOUT_DEBUG, +}; + +std::unique_ptr createType_out(SceFileOutChannel ch); diff --git a/core/fileManager/types/type_random.cpp b/core/fileManager/types/type_random.cpp new file mode 100644 index 00000000..23b60b33 --- /dev/null +++ b/core/fileManager/types/type_random.cpp @@ -0,0 +1,50 @@ +#include "type_random.h" + +class TypeRandom: public IFile { + public: + TypeRandom(): IFile(FileType::Device) {} + + virtual ~TypeRandom() {} + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_random() { + std::srand(std::time(nullptr)); + return std::make_unique(); +} + +size_t TypeRandom::read(void* buf, size_t nbytes) { + auto cbuf = static_cast(buf); + + for (size_t i = 0; i < nbytes; i++) + cbuf[i] = std::rand() & 0xFF; + + return nbytes; +} + +size_t TypeRandom::write(void* buf, size_t nbytes) { + return 0; +} + +void TypeRandom::sync() {} + +int TypeRandom::ioctl(int request, SceVariadicList argp) { + return 0; +} + +int TypeRandom::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeRandom::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_random.h b/core/fileManager/types/type_random.h new file mode 100644 index 00000000..1dd12346 --- /dev/null +++ b/core/fileManager/types/type_random.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../ifile.h" + +#include + +std::unique_ptr createType_random(); diff --git a/core/fileManager/types/type_rng.cpp b/core/fileManager/types/type_rng.cpp new file mode 100644 index 00000000..3c3abdb4 --- /dev/null +++ b/core/fileManager/types/type_rng.cpp @@ -0,0 +1,63 @@ +#include "logging.h" +#include "type_random.h" + +LOG_DEFINE_MODULE(IODevice_RNG); + +class TypeRNG: public IFile { + public: + TypeRNG(): IFile(FileType::Device) {} + + virtual ~TypeRNG() {} + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_rng() { + std::srand(std::time(nullptr)); + return std::make_unique(); +} + +size_t TypeRNG::read(void* buf, size_t nbytes) { + auto cbuf = static_cast(buf); + + for (size_t i = 0; i < nbytes; i++) + cbuf[i] = std::rand() & 0xFF; + + return nbytes; +} + +size_t TypeRNG::write(void* buf, size_t nbytes) { + return 0; +} + +void TypeRNG::sync() {} + +int TypeRNG::ioctl(int request, SceVariadicList argp) { + LOG_USE_MODULE(IODevice_RNG); + + switch (request) { + // These are unknown for now + case 0x40445301: break; + case 0x40445302: break; + + default: LOG_ERR(L"Unknown ioctl request to /dev/rng: %d", request); return -1; + } + + return 0; +} + +int TypeRNG::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeRNG::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_rng.h b/core/fileManager/types/type_rng.h new file mode 100644 index 00000000..8141762b --- /dev/null +++ b/core/fileManager/types/type_rng.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../ifile.h" + +#include + +std::unique_ptr createType_rng(); diff --git a/core/fileManager/types/type_zero.cpp b/core/fileManager/types/type_zero.cpp new file mode 100644 index 00000000..c9e8b4ad --- /dev/null +++ b/core/fileManager/types/type_zero.cpp @@ -0,0 +1,48 @@ +#include "type_zero.h" + +class TypeZero: public IFile { + public: + TypeZero(): IFile(FileType::Device) {} + + virtual ~TypeZero() {} + + // ### Interface + size_t read(void* buf, size_t nbytes) final; + size_t write(void* buf, size_t nbytes) final; + void sync() final; + int ioctl(int request, SceVariadicList argp) final; + int fcntl(int cmd, SceVariadicList argp) final; + int64_t lseek(int64_t offset, SceWhence whence) final; + + void* getNative() final { return nullptr; } +}; + +std::unique_ptr createType_zero() { + return std::make_unique(); +} + +size_t TypeZero::read(void* buf, size_t nbytes) { + for (size_t i = 0; i < nbytes; i++) { + ((char*)buf)[i] = '\0'; + } + + return nbytes; +} + +size_t TypeZero::write(void* buf, size_t nbytes) { + return nbytes; +} + +void TypeZero::sync() {} + +int TypeZero::ioctl(int request, SceVariadicList argp) { + return 0; +} + +int TypeZero::fcntl(int cmd, SceVariadicList argp) { + return 0; +} + +int64_t TypeZero::lseek(int64_t offset, SceWhence whence) { + return -1; +} diff --git a/core/fileManager/types/type_zero.h b/core/fileManager/types/type_zero.h new file mode 100644 index 00000000..ecb270a4 --- /dev/null +++ b/core/fileManager/types/type_zero.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../ifile.h" + +#include + +std::unique_ptr createType_zero(); diff --git a/core/hotkeys/CMakeLists.txt b/core/hotkeys/CMakeLists.txt new file mode 100644 index 00000000..89d2de75 --- /dev/null +++ b/core/hotkeys/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library(hotkeys OBJECT + hotkeys.cpp +) + +add_dependencies(hotkeys third_party config_emu) + +target_compile_definitions(hotkeys PUBLIC WIN32_LEAN_AND_MEAN) + +file(COPY hotkeys.h DESTINATION ${DST_INCLUDE_DIR}/hotkeys) \ No newline at end of file diff --git a/core/hotkeys/hotkeys.cpp b/core/hotkeys/hotkeys.cpp new file mode 100644 index 00000000..8cd2a99f --- /dev/null +++ b/core/hotkeys/hotkeys.cpp @@ -0,0 +1,149 @@ +#define __APICALL_EXTERN +#include "hotkeys.h" +#undef __APICALL_EXTERN + +#include "config_emu.h" + +#include + +class Hotkeys: public IHotkeys { + private: + struct HkBacks { + HkCommand cmd; + HkFunc func; + }; + + struct KBind { + SDL_Keycode kc; + uint16_t mod; + }; + + std::vector m_cbacks = {}; + std::array m_binds = {{SDLK_UNKNOWN, 0}}; + uint32_t m_state = 0; + + HkCommand lookupCommand(SDL_Keycode kc, uint16_t mod) { + for (uint32_t c = uint32_t(HkCommand::UNKNOWN); c < uint32_t(HkCommand::COMMANDS_MAX); ++c) { + auto& bind = m_binds[(uint32_t)c]; + if (kc == bind.kc && mod == bind.mod) return HkCommand(c); + } + + return HkCommand::UNKNOWN; + } + + public: + Hotkeys() { + static std::unordered_map map = { + {"gamereport.send", HkCommand::GAMEREPORT_SEND_REPORT}, + {"overlay.open", HkCommand::OVERLAY_MENU}, + + {"controller.l3", HkCommand::CONTROLLER_L3}, + {"controller.r3", HkCommand::CONTROLLER_R3}, + {"controller.options", HkCommand::CONTROLLER_OPTIONS}, + {"controller.dpad_up", HkCommand::CONTROLLER_DPAD_UP}, + {"controller.dpad_right", HkCommand::CONTROLLER_DPAD_RIGHT}, + {"controller.dpad_down", HkCommand::CONTROLLER_DPAD_DOWN}, + {"controller.dpad_left", HkCommand::CONTROLLER_DPAD_LEFT}, + {"controller.l2", HkCommand::CONTROLLER_L2}, + {"controller.r2", HkCommand::CONTROLLER_R2}, + {"controller.l1", HkCommand::CONTROLLER_L1}, + {"controller.r1", HkCommand::CONTROLLER_R1}, + {"controller.triangle", HkCommand::CONTROLLER_TRIANGLE}, + {"controller.circle", HkCommand::CONTROLLER_CIRCLE}, + {"controller.cross", HkCommand::CONTROLLER_CROSS}, + {"controller.square", HkCommand::CONTROLLER_SQUARE}, + {"controller.touchpad", HkCommand::CONTROLLER_TOUCH_PAD}, + {"controller.lx+", HkCommand::CONTROLLER_LX_UP}, + {"controller.lx-", HkCommand::CONTROLLER_LX_DOWN}, + {"controller.ly+", HkCommand::CONTROLLER_LY_UP}, + {"controller.ly-", HkCommand::CONTROLLER_LY_DOWN}, + {"controller.rx+", HkCommand::CONTROLLER_RX_UP}, + {"controller.rx-", HkCommand::CONTROLLER_RX_DOWN}, + {"controller.ry+", HkCommand::CONTROLLER_RY_UP}, + {"controller.ry-", HkCommand::CONTROLLER_RY_DOWN}, + }; + + auto readBind = [](std::string& str, KBind* kb) -> bool { + if (str.length() == 0) return false; + auto delim = str.find_last_of("||"); + if (delim != std::string::npos) { + auto mod = str.substr(0, delim - 1); + auto key = str.substr(delim + 1); + kb->kc = SDL_GetScancodeFromName(key.c_str()); + + switch (SDL_GetScancodeFromName(mod.c_str())) { + case SDL_SCANCODE_LALT: kb->mod = KMOD_LALT; return true; + case SDL_SCANCODE_LCTRL: kb->mod = KMOD_LCTRL; return true; + case SDL_SCANCODE_LSHIFT: kb->mod = KMOD_LSHIFT; return true; + + case SDL_SCANCODE_RALT: kb->mod = KMOD_RALT; return true; + case SDL_SCANCODE_RCTRL: kb->mod = KMOD_RCTRL; return true; + case SDL_SCANCODE_RSHIFT: kb->mod = KMOD_RSHIFT; return true; + + default: return false; + } + } + + if ((kb->kc = SDL_GetScancodeFromName(str.c_str())) != SDL_SCANCODE_UNKNOWN) { + kb->mod = 0x0000; + return true; + } + + return false; + }; + + auto [lock, jData] = accessConfig()->accessModule(ConfigModFlag::CONTROLS); + for (auto& [bname, sdlkeys]: (*jData)["keybinds"].items()) { + auto it = map.find(bname.c_str()); + if (it == map.end()) continue; + std::string temp; + sdlkeys.get_to(temp); + + auto& bind = m_binds[(uint32_t)it->second]; + if (!readBind(temp, &bind)) { + printf("Missing key binding for \"%s\"!\n", bname.c_str()); + bind = {0, 0}; + continue; + } + + for (uint32_t c = uint32_t(HkCommand::UNKNOWN); c < uint32_t(HkCommand::COMMANDS_MAX); ++c) { + auto& tbind = m_binds[(uint32_t)c]; + if (bind.kc == SDL_SCANCODE_UNKNOWN || tbind.kc == SDL_SCANCODE_UNKNOWN) continue; + if (tbind.kc == bind.kc && tbind.mod == bind.mod && (uint32_t)it->second != c) { + printf("Key conflict found! Please rebind your \"%s\" key.\n", bname.c_str()); + bind = {0, 0}; + continue; + } + } + } + } + + void registerCallback(HkCommand cmd, HkFunc func) final { m_cbacks.emplace_back(cmd, func); } + + bool isPressed(HkCommand cmd) final { return (m_state & (1ull << (uint8_t)cmd)) != 0; } + + int32_t isPressedEx(HkCommand cmd1, HkCommand cmd2, int32_t ifcmd1, int32_t ifcmd2, int32_t def) final { + return isPressed(cmd1) ? ifcmd1 : (isPressed(cmd2) ? ifcmd2 : def); + } + + void update(const SDL_KeyboardEvent* event) final { + bool pressed = event->type == SDL_KEYDOWN; + HkCommand cmd = lookupCommand(event->keysym.scancode, event->keysym.mod & 0x03C3); + if (cmd == HkCommand::UNKNOWN) return; + uint32_t bit = (1ull << (uint8_t)cmd); + + if (pressed && ((m_state & bit) == 0)) { + for (auto& hkc: m_cbacks) + if (hkc.cmd == cmd) hkc.func(cmd); + + m_state |= bit; + } else if (!pressed && ((m_state & bit) != 0)) { + m_state &= ~bit; + } + } +}; + +IHotkeys& accessHotkeys() { + static Hotkeys ih; + return ih; +} diff --git a/core/hotkeys/hotkeys.h b/core/hotkeys/hotkeys.h new file mode 100644 index 00000000..24d53757 --- /dev/null +++ b/core/hotkeys/hotkeys.h @@ -0,0 +1,72 @@ +#pragma once + +#include "utility/utility.h" + +#include +#include + +enum class HkCommand { + UNKNOWN = 0, + + // System commands + GAMEREPORT_SEND_REPORT, + OVERLAY_MENU, + + // Gamepad emulation + CONTROLLER_L3, + CONTROLLER_R3, + CONTROLLER_OPTIONS, + CONTROLLER_DPAD_UP, + CONTROLLER_DPAD_RIGHT, + CONTROLLER_DPAD_DOWN, + CONTROLLER_DPAD_LEFT, + CONTROLLER_L2, + CONTROLLER_R2, + CONTROLLER_L1, + CONTROLLER_R1, + CONTROLLER_TRIANGLE, + CONTROLLER_CIRCLE, + CONTROLLER_CROSS, + CONTROLLER_SQUARE, + CONTROLLER_TOUCH_PAD, + CONTROLLER_LX_UP, + CONTROLLER_LX_DOWN, + CONTROLLER_LY_UP, + CONTROLLER_LY_DOWN, + CONTROLLER_RX_UP, + CONTROLLER_RX_DOWN, + CONTROLLER_RY_UP, + CONTROLLER_RY_DOWN, + + /** + * Warning! If this value ever exceeds 32 you should increase + * the size of `m_state` variable in `class Hotkeys` aswell! + */ + COMMANDS_MAX, +}; + +typedef std::function HkFunc; + +class IHotkeys { + CLASS_NO_COPY(IHotkeys); + CLASS_NO_MOVE(IHotkeys); + + public: + IHotkeys() = default; + virtual ~IHotkeys() = default; + + virtual void registerCallback(HkCommand cmd, HkFunc func) = 0; + virtual bool isPressed(HkCommand cmd) = 0; + virtual int32_t isPressedEx(HkCommand cmd1, HkCommand cmd2, int32_t ifcmd1, int32_t ifcmd2, int32_t def) = 0; + virtual void update(const SDL_KeyboardEvent* event) = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif +__APICALL IHotkeys& accessHotkeys(); +#undef __APICALL diff --git a/core/imports/exports/pm4_custom.h b/core/imports/exports/pm4_custom.h new file mode 100644 index 00000000..0a1c679a --- /dev/null +++ b/core/imports/exports/pm4_custom.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Pm4 { + +#define PM4_GET(u, r, f) (((u) >> Pm4::r##_##f##_SHIFT) & Pm4::r##_##f##_MASK) + +enum class Custom : uint8_t { + R_VS, + R_PS, + R_DRAW_INDEX, + R_DRAW_INDEX_AUTO, + R_DRAW_INDEX_OFFSET, + R_DRAW_RESET, + R_WAIT_FLIP_DONE, + R_CS, + R_DISPATCH_DIRECT, + R_DISPATCH_RESET, + R_DISPATCH_WAIT_MEM, + R_PUSH_MARKER, + R_POP_MARKER, + R_VS_EMBEDDED, + R_PS_EMBEDDED, + R_VS_UPDATE, + R_PS_UPDATE, + R_VGT_CONTROL, + R_NUM_COMMANDS, // used for array size +}; + +constexpr uint32_t create(uint16_t len, Custom baseIndex) { + return 0x60000000u | (((len - 2u) & 0x3fffu) << 8u) | (uint8_t)baseIndex; +} + +} // namespace Pm4 \ No newline at end of file diff --git a/core/imports/exports/readme.md b/core/imports/exports/readme.md new file mode 100644 index 00000000..338156ae --- /dev/null +++ b/core/imports/exports/readme.md @@ -0,0 +1,3 @@ +## Exports + +Exports currently provided by the emulator itself. \ No newline at end of file diff --git a/core/initParams/CMakeLists.txt b/core/initParams/CMakeLists.txt new file mode 100644 index 00000000..dee295a4 --- /dev/null +++ b/core/initParams/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(initParams OBJECT + initParams.cpp +) + +add_dependencies(initParams third_party) + +file(COPY initParams.h DESTINATION ${DST_INCLUDE_DIR}/initParams) \ No newline at end of file diff --git a/core/initParams/initParams.cpp b/core/initParams/initParams.cpp new file mode 100644 index 00000000..262cfbda --- /dev/null +++ b/core/initParams/initParams.cpp @@ -0,0 +1,101 @@ +#define __APICALL_INITPARAMS_EXTERN +#include "initParams.h" +#undef __APICALL_INITPARAMS_EXTERN +#include +#include +#include + +class InitParams: public IInitParams { + boost::program_options::variables_map m_vm; + + public: + InitParams() = default; + + bool init(int argc, char** argv) final; + bool isDebug() final; + + std::string getApplicationPath() final; + std::string getApplicationRoot() final; + std::string getUpdateRoot() final; + + bool enableValidation() final; + bool enableBrightness() final; + bool useVSYNC() final; + bool try4K() final; + + virtual ~InitParams() = default; +}; + +bool InitParams::init(int argc, char** argv) { + namespace po = boost::program_options; + + po::options_description desc("Allowed options"); + desc.add_options() + // clang-format off + ("help,h", "Help") + ("d", "Wait for debugger") + ("vkValidation", "Enable vulkan validation layers") + ("bright", "use srgb display format (brightness)") + ("4k", "try 4K display mode if game supports it") + ("vsync", po::value()->default_value(true), "Enable vertical synchronization") + ("file", po::value(), "fullpath to applications binary") + ("root", po::value(), "Applications root") + ("update", po::value(), "update files folder") + // clang-format on + ; + + try { + po::store(po::parse_command_line(argc, argv, desc), m_vm); + po::notify(m_vm); + } catch (...) { + return false; + } + + if (m_vm.count("help")) { + std::cout << desc << '\n'; + return false; + } + if (m_vm.count("file") == 0) { + std::cout << "--file missing\n"; + return false; + } + + return true; +} + +IInitParams* accessInitParams() { + static InitParams obj; + return &obj; +} + +bool InitParams::isDebug() { + return m_vm.count("d"); +} + +std::string InitParams::getApplicationPath() { + return m_vm.count("file") ? m_vm["file"].as() : std::string(); +} + +std::string InitParams::getApplicationRoot() { + return m_vm.count("root") ? m_vm["root"].as() : std::string(); +} + +std::string InitParams::getUpdateRoot() { + return m_vm.count("update") ? m_vm["update"].as() : std::string(); +} + +bool InitParams::enableValidation() { + return m_vm.count("vkValidation"); +} + +bool InitParams::enableBrightness() { + return m_vm.count("bright"); +} + +bool InitParams::useVSYNC() { + return m_vm["vsync"].as(); +} + +bool InitParams::try4K() { + return m_vm.count("4k"); +} diff --git a/core/initParams/initParams.h b/core/initParams/initParams.h new file mode 100644 index 00000000..2f2f47a1 --- /dev/null +++ b/core/initParams/initParams.h @@ -0,0 +1,37 @@ +#pragma once +#include "utility/utility.h" + +#include + +class IInitParams { + CLASS_NO_COPY(IInitParams); + CLASS_NO_MOVE(IInitParams); + + public: + IInitParams() = default; + + virtual bool init(int argc, char** argv) = 0; + virtual bool isDebug() = 0; + + virtual std::string getApplicationPath() = 0; + virtual std::string getApplicationRoot() = 0; + virtual std::string getUpdateRoot() = 0; + + virtual bool enableValidation() = 0; + virtual bool enableBrightness() = 0; + virtual bool useVSYNC() = 0; + virtual bool try4K() = 0; + + virtual ~IInitParams() = default; +}; + +#if defined(__APICALL_INITPARAMS_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL IInitParams* accessInitParams(); +#undef __APICALL \ No newline at end of file diff --git a/core/kernel/CMakeLists.txt b/core/kernel/CMakeLists.txt new file mode 100644 index 00000000..97fc2b50 --- /dev/null +++ b/core/kernel/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(kernel OBJECT + eventqueue.cpp + eventflag.cpp + errors.cpp + filesystem.cpp + pthread.cpp + semaphore_fifo.cpp +) + +add_dependencies(kernel third_party) + +file(COPY errors.h eventflag.h eventqueue_types.h eventqueue.h filesystem.h pthread.h pthread_types.h eventqueue.h semaphore.h DESTINATION ${DST_INCLUDE_DIR}/kernel) \ No newline at end of file diff --git a/core/kernel/errors.cpp b/core/kernel/errors.cpp new file mode 100644 index 00000000..16c88264 --- /dev/null +++ b/core/kernel/errors.cpp @@ -0,0 +1,13 @@ +#define __APICALL_EXTERN +#include "errors.h" +#undef __APICALL_EXTERN + +int* getError_pthread() { + thread_local int g_errno = 0; + return &g_errno; +} + +void setError_pthread(int error) { + // todo tracing stacktrace? + *getError_pthread() = error; +} diff --git a/core/kernel/errors.h b/core/kernel/errors.h new file mode 100644 index 00000000..48e88202 --- /dev/null +++ b/core/kernel/errors.h @@ -0,0 +1,33 @@ +#pragma once +#include "modules_include/common.h" +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL int* getError_pthread(); +__APICALL void setError_pthread(int); + +/** + * @brief Takes Kernel errors from gerErr(), converts and saves it + * + * @param result from gerErr() + * @return int32_t + */ +static int32_t POSIX_CALL(int32_t result) { + if (result >= 0) return result; + + int res = result - (int32_t)0x80020000; + setError_pthread(res); + return res; +} + +static int32_t POSIX_SET(ErrCode error) { + setError_pthread((int32_t)error); + return -1; +} + +#undef __APICALL diff --git a/core/kernel/eventflag.cpp b/core/kernel/eventflag.cpp new file mode 100644 index 00000000..01d36bc4 --- /dev/null +++ b/core/kernel/eventflag.cpp @@ -0,0 +1,177 @@ +#define __APICALL_EXTERN +#include "eventflag.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "modules_include/common.h" + +#include +#include + +LOG_DEFINE_MODULE(KernelEventFlag); + +namespace Kernel::EventFlag { + +class KernelEventFlag: public IKernelEventFlag { + CLASS_NO_COPY(KernelEventFlag); + + public: + KernelEventFlag(std::string const& name, bool single, bool /*fifo*/, uint64_t bits): m_singleThread(single), m_bits(bits), m_name(name) {}; + virtual ~KernelEventFlag(); + + void set(uint64_t bits) final; + void clear(uint64_t bits) final; + void cancel(uint64_t bits, int* num_waiting_threads) final; + + int wait(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result, uint32_t* ptr_micros) final; + + int poll(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result) final { + uint32_t micros = 0; + return wait(bits, wait_mode, clear_mode, result, µs); + } + + private: + enum class Status { Set, Canceled, Deleted }; + + void setStatus(Status status) { + m_status = status; + if (status == Status::Set) m_cond_var_status.notify_all(); + } + + boost::mutex m_mutex_cond; + boost::condition_variable m_cond_var; + boost::condition_variable m_cond_var_status; + + Status m_status = Status::Set; + + int m_waitingThreads = 0; + bool m_singleThread = false; + + std::string m_name; + + uint64_t m_bits = 0; +}; + +KernelEventFlag::~KernelEventFlag() { + boost::unique_lock lock(m_mutex_cond); + + m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; }); + + m_status = Status::Deleted; + m_cond_var.notify_all(); + + m_cond_var_status.wait(lock, [this] { return m_waitingThreads == 0; }); +} + +void KernelEventFlag::set(uint64_t bits) { + boost::unique_lock lock(m_mutex_cond); + + m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; }); + m_bits |= bits; + m_cond_var.notify_all(); +} + +void KernelEventFlag::clear(uint64_t bits) { + boost::unique_lock lock(m_mutex_cond); + + m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; }); + m_bits &= bits; +} + +void KernelEventFlag::cancel(uint64_t bits, int* num_waiting_threads) { + boost::unique_lock lock(m_mutex_cond); + + m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; }); + if (num_waiting_threads != nullptr) { + *num_waiting_threads = m_waitingThreads; + } + + setStatus(Status::Canceled); + m_bits = bits; + + m_cond_var.notify_all(); + m_cond_var_status.wait(lock, [this] { return m_waitingThreads == 0; }); + setStatus(Status::Set); +} + +int KernelEventFlag::wait(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result, uint32_t* ptr_micros) { + LOG_USE_MODULE(KernelEventFlag); + boost::unique_lock lock(m_mutex_cond); + + if (m_singleThread && m_waitingThreads > 0) { + return getErr(ErrCode::_EPERM); + } + + uint32_t micros = 0; + bool infinitely = true; + if (ptr_micros != nullptr) { + micros = *ptr_micros; + infinitely = false; + } + + auto const start = boost::chrono::system_clock::now(); + ++m_waitingThreads; + auto waitFunc = [this, wait_mode, bits] { + return (m_status == Status::Canceled || m_status == Status::Deleted || (wait_mode == WaitMode::And && (m_bits & bits) == bits) || + (wait_mode == WaitMode::Or && (m_bits & bits) != 0)); + }; + if (infinitely) { + m_cond_var.wait(lock, waitFunc); + } else { + if (!m_cond_var.wait_for(lock, boost::chrono::microseconds(micros), waitFunc)) { + if (result != nullptr) { + *result = m_bits; + } + *ptr_micros = 0; + --m_waitingThreads; + return getErr(ErrCode::_ETIMEDOUT); + } + } + --m_waitingThreads; + m_cond_var_status.notify_all(); + if (result != nullptr) { + *result = m_bits; + } + + auto elapsed = boost::chrono::duration_cast(boost::chrono::system_clock::now() - start).count(); + if (result != nullptr) { + *result = m_bits; + } + + if (ptr_micros != nullptr) { + *ptr_micros = (elapsed >= micros ? 0 : micros - elapsed); + // LOG_TRACE(L"wait %llu us -> %llu us", micros, elapsed); + } + + // Aborted + if (m_status == Status::Canceled) { + return getErr(ErrCode::_ECANCELED); + } else if (m_status == Status::Deleted) { + return getErr(ErrCode::_EINTR); + } + // - + + if (clear_mode == ClearMode::All) { + m_bits = 0; + } else if (clear_mode == ClearMode::Bits) { + m_bits &= ~bits; + } + + return Ok; +} + +IKernelEventFlag_t createEventFlag(std::string const& name, bool single, bool fifo, uint64_t bits) { + return std::make_unique(std::string(name), single, fifo, bits).release(); +} + +int deleteEventFlag(IKernelEventFlag_t ef) { + LOG_USE_MODULE(KernelEventFlag); + if (ef == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + LOG_INFO(L"Deleted ptr:0x%08llx", (uint64_t)ef); + delete ef; + return Ok; +} +} // namespace Kernel::EventFlag diff --git a/core/kernel/eventflag.h b/core/kernel/eventflag.h new file mode 100644 index 00000000..0fc77e94 --- /dev/null +++ b/core/kernel/eventflag.h @@ -0,0 +1,44 @@ +#pragma once + +#include "utility/utility.h" + +namespace Kernel::EventFlag { + +enum class ClearMode { None, All, Bits }; + +enum class WaitMode { And, Or }; + +class IKernelEventFlag { + CLASS_NO_COPY(IKernelEventFlag); + CLASS_NO_MOVE(IKernelEventFlag); + + protected: + IKernelEventFlag() = default; + + public: + virtual ~IKernelEventFlag() = default; + + virtual void set(uint64_t bits) = 0; + virtual void clear(uint64_t bits) = 0; + virtual void cancel(uint64_t bits, int* num_waiting_threads) = 0; + + virtual int wait(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result, uint32_t* ptr_micros) = 0; + + virtual int poll(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result) = 0; +}; + +using IKernelEventFlag_t = IKernelEventFlag*; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL IKernelEventFlag_t createEventFlag(std::string const& name, bool single, bool fifo, uint64_t bits); +__APICALL int deleteEventFlag(IKernelEventFlag_t ef); + +#undef __APICALL +} // namespace Kernel::EventFlag \ No newline at end of file diff --git a/core/kernel/eventqueue.cpp b/core/kernel/eventqueue.cpp new file mode 100644 index 00000000..4605cf94 --- /dev/null +++ b/core/kernel/eventqueue.cpp @@ -0,0 +1,298 @@ +#define __APICALL_EXTERN +#include "eventqueue.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "modules_include/common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Kernel { +namespace EventQueue { +LOG_DEFINE_MODULE(EventQueue); + +class KernelEqueue: public IKernelEqueue { + public: + KernelEqueue() = default; + virtual ~KernelEqueue(); + + [[nodiscard]] std::string const& getName() const { return m_name; } + + void setName(std::string&& name) { m_name.swap(name); } + + int waitForEvents(KernelEvent_t ev, int num, SceKernelUseconds const* micros); + + int pollEvents(KernelEvent_t ev, int num) { + std::unique_lock lock(m_mutex_cond); + auto ret = getTriggeredEvents(ev, num); + + return ret; + } + + int wait(KernelEvent_t ev, int num, int* out, const SceKernelUseconds* time) final; + + int addUserEvent(int ident) final; + int addUserEventEdge(int ident) final; + int triggerUserEvent(uintptr_t ident, void* udata) final; + + int addEvent(const KernelEqueueEvent& event) final; + int triggerEvent(uintptr_t ident, int16_t filter, void* trigger_data) final; + int deleteEvent(uintptr_t ident, int16_t filter) final; + + private: + int getTriggeredEvents(KernelEvent_t ev, int num); + + std::list m_events; // We dont ecpext many events ~5 + + boost::mutex m_mutex_cond; + std::mutex m_mutex_int; + boost::condition_variable m_cond_var; + + std::string m_name; + bool m_closed = false; +}; + +KernelEqueue::~KernelEqueue() { + std::unique_lock const lock(m_mutex_cond); + /* + for (auto& ev: m_events) { + if (ev.filter.delete_event_func != nullptr) { + ev.filter.delete_event_func(this, &ev); + } + }*/ + m_closed = true; + m_cond_var.notify_all(); +} + +int KernelEqueue::waitForEvents(KernelEvent_t ev, int num, SceKernelUseconds const* micros) { + LOG_USE_MODULE(EventQueue); + // LOG_TRACE(L"->waitForEvents: ident:0x%08llx num:%d", ev->ident, num); + + boost::unique_lock lock(m_mutex_cond); + + auto hasEvents = [&] { + for (auto& ev: m_events) { + if (ev.triggered) { + return true; + } + } + return false; + }; + + if (micros != nullptr) { + m_cond_var.wait_for(lock, boost::chrono::microseconds(*micros), [&] { return hasEvents() | m_closed; }); + } else { + m_cond_var.wait(lock, [&] { return hasEvents() | m_closed; }); + } + + // LOG_TRACE(L"<-waitForEvents: ident:0x%08llx ret:%d", ev->ident, ret); + return getTriggeredEvents(ev, num); +} + +int KernelEqueue::getTriggeredEvents(KernelEvent_t eventList, int num) { + LOG_USE_MODULE(EventQueue); + + int countTriggered = 0; + for (auto& ev: m_events) { + if (ev.triggered) { + eventList[countTriggered++] = ev.event; + + if ((((uint32_t)ev.event.flags & (uint32_t)EventFlags::EV_CLEAR) != 0) && ev.filter.reset_func != nullptr) { + ev.filter.reset_func(&ev); + } + LOG_TRACE(L"Event Triggered: ident:0x%08llx, filter:%d", (uint64_t)ev.event.ident, ev.event.filter); + if (countTriggered >= num) { + break; + } + } + } + + return countTriggered; +} + +void event_trigger_func(Kernel::EventQueue::KernelEqueueEvent* event, void* trigger_data) { + event->triggered = true; + event->event.fflags++; + event->event.udata = trigger_data; +} + +void event_reset_func(Kernel::EventQueue::KernelEqueueEvent* event) { + event->triggered = false; + event->event.fflags = 0; + event->event.udata = 0; +} + +void event_delete_func(Kernel::EventQueue::IKernelEqueue_t eq, Kernel::EventQueue::KernelEqueueEvent* event) {} + +int createEqueue(IKernelEqueue_t* eq, const char* name) { + LOG_USE_MODULE(EventQueue); + if (eq == nullptr || name == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto item = std::make_unique(); + item->setName(std::string(name)); + + LOG_INFO(L"+EventQueue (%S)", item->getName().c_str()); + *eq = item.release(); + return Ok; +} + +int deleteEqueue(IKernelEqueue_t eq) { + LOG_USE_MODULE(EventQueue); + + if (eq == nullptr) { + return getErr(ErrCode::_EBADF); + } + + LOG_INFO(L"-EventQueue (%S)", ((KernelEqueue*)eq)->getName().c_str()); + delete (eq); + + return Ok; +} + +int KernelEqueue::wait(KernelEvent_t ev, int num, int* out, const SceKernelUseconds* time) { + if (ev == nullptr) { + return getErr(ErrCode::_EFAULT); + } + + if (num < 1) { + return getErr(ErrCode::_EINVAL); + } + + if (time == nullptr) { + *out = waitForEvents(ev, num, nullptr); + if (*out == 0) { + return getErr(ErrCode::_ETIMEDOUT); + } + } else { + *out = waitForEvents(ev, num, time); + if (*out == 0 && *time > 0) { + return getErr(ErrCode::_ETIMEDOUT); + } + } + + return Ok; +} + +int KernelEqueue::addUserEvent(int ident) { + return addEvent(Kernel::EventQueue::KernelEqueueEvent {.triggered = false, + .event = + { + .ident = ident, + .filter = Kernel::EventQueue::KERNEL_EVFILT_USER, + .flags = Kernel::EventQueue::EventFlags::EV_NONE, + .fflags = 0, + .data = 0, + .udata = 0, + + }, + .filter { + .data = nullptr, + .trigger_func = event_trigger_func, + .reset_func = event_reset_func, + .delete_event_func = event_delete_func, + }}); +} + +int KernelEqueue::addUserEventEdge(int ident) { + return addEvent(Kernel::EventQueue::KernelEqueueEvent {.triggered = false, + .event = + { + .ident = ident, + .filter = Kernel::EventQueue::KERNEL_EVFILT_USER, + .flags = Kernel::EventQueue::EventFlags::EV_CLEAR, + .fflags = 0, + .data = 0, + .udata = 0, + + }, + .filter { + .data = nullptr, + .trigger_func = event_trigger_func, + .reset_func = event_reset_func, + .delete_event_func = event_delete_func, + }}); +} + +int KernelEqueue::triggerUserEvent(uintptr_t ident, void* udata) { + LOG_USE_MODULE(EventQueue); + LOG_TRACE(L"triggerUserEvent: ident:0x%08llx", (uint64_t)ident); + + return triggerEvent(ident, Kernel::EventQueue::KERNEL_EVFILT_USER, udata); +} + +int KernelEqueue::addEvent(const KernelEqueueEvent& event) { + LOG_USE_MODULE(EventQueue); + LOG_INFO(L"(%S) Add Event: ident:0x%08llx, filter:%d", m_name.c_str(), (uint64_t)event.event.ident, event.event.filter); + + std::unique_lock lock(m_mutex_cond); + + auto it = std::find_if(m_events.begin(), m_events.end(), + [=](KernelEqueueEvent const& ev) { return ev.event.ident == event.event.ident && ev.event.filter == event.event.filter; }); + + if (it != m_events.end()) { + *it = event; + } else { + m_events.push_back(event); + } + + if (event.triggered) { + lock.unlock(); + m_cond_var.notify_all(); + } + + return Ok; +} + +int KernelEqueue::triggerEvent(uintptr_t ident, int16_t filter, void* trigger_data) { + LOG_USE_MODULE(EventQueue); + LOG_TRACE(L"triggerEvent: ident:0x%08llx, filter:%d", (uint64_t)ident, filter); + + std::unique_lock lock(m_mutex_cond); + auto it = std::find_if(m_events.begin(), m_events.end(), [=](KernelEqueueEvent const& ev) { return ev.event.ident == ident && ev.event.filter == filter; }); + + if (it != m_events.end()) { + if (it->filter.trigger_func != nullptr) { + it->filter.trigger_func(&(*it), trigger_data); + } else { + it->triggered = true; + it->event.fflags++; + } + lock.unlock(); + m_cond_var.notify_all(); + return Ok; + } + + return getErr(ErrCode::_ENOENT); +} + +int KernelEqueue::deleteEvent(uintptr_t ident, int16_t filter) { + LOG_USE_MODULE(EventQueue); + LOG_INFO(L"deleteEvent: ident:0x%08llx, filter:%llu", (uint64_t)ident, filter); + + std::unique_lock const lock(m_mutex_cond); + auto it = std::find_if(m_events.begin(), m_events.end(), [=](KernelEqueueEvent const& ev) { return ev.event.ident == ident && ev.event.filter == filter; }); + + if (it != m_events.end()) { + if (it->filter.delete_event_func != nullptr) { + it->filter.delete_event_func(this, &(*it)); + } + + m_events.erase(it); + return Ok; + } + + return getErr(ErrCode::_ENOENT); +} +} // namespace EventQueue +} // namespace Kernel \ No newline at end of file diff --git a/core/kernel/eventqueue.h b/core/kernel/eventqueue.h new file mode 100644 index 00000000..5d4796df --- /dev/null +++ b/core/kernel/eventqueue.h @@ -0,0 +1,41 @@ +#pragma once +#include "eventqueue_types.h" +#include "modules_include/common.h" +#include "utility/utility.h" + +namespace Kernel::EventQueue { +class IKernelEqueue { + CLASS_NO_COPY(IKernelEqueue); + CLASS_NO_MOVE(IKernelEqueue); + + protected: + IKernelEqueue() = default; + + public: + virtual ~IKernelEqueue() = default; + + virtual int wait(KernelEvent_t ev, int num, int* out, const SceKernelUseconds* timo) = 0; + + virtual int addUserEvent(int ident) = 0; + virtual int addUserEventEdge(int ident) = 0; + virtual int triggerUserEvent(uintptr_t ident, void* udata) = 0; + + virtual int addEvent(const KernelEqueueEvent& event) = 0; + virtual int triggerEvent(uintptr_t ident, int16_t filter, void* trigger_data) = 0; + virtual int deleteEvent(uintptr_t ident, int16_t filter) = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL int createEqueue(IKernelEqueue_t* eq, const char* name); +__APICALL int deleteEqueue(IKernelEqueue_t eq); + +#undef __APICALL + +} // namespace Kernel::EventQueue \ No newline at end of file diff --git a/core/kernel/eventqueue_types.h b/core/kernel/eventqueue_types.h new file mode 100644 index 00000000..dfa4e55e --- /dev/null +++ b/core/kernel/eventqueue_types.h @@ -0,0 +1,55 @@ +#pragma once +#include + +namespace Kernel::EventQueue { +constexpr int16_t KERNEL_EVFILT_TIMER = -7; +constexpr int16_t KERNEL_EVFILT_READ = -1; +constexpr int16_t KERNEL_EVFILT_WRITE = -2; +constexpr int16_t KERNEL_EVFILT_USER = -11; +constexpr int16_t KERNEL_EVFILT_FILE = -4; +constexpr int16_t KERNEL_EVFILT_GRAPHICS = -14; +constexpr int16_t KERNEL_EVFILT_VIDEO_OUT = -13; +constexpr int16_t KERNEL_EVFILT_HRTIMER = -15; + +enum class EventFlags : uint32_t { + EV_NONE = 0, + EV_ONESHOT = 0x10, // only report one occurrence + EV_CLEAR = 0x20, // clear event state after reporting + EV_RECEIPT = 0x40, // force EV_ERROR on success, data=0 + EV_DISPATCH = 0x80, // disable event after reporting + + EV_SYSFLAGS = 0xF000, // reserved by system + EV_FLAG1 = 0x2000, // filter-specific flag +}; + +struct KernelEvent { + int ident = 0; + int16_t filter = 0; + EventFlags flags = EventFlags::EV_NONE; + uint32_t fflags = 0; + intptr_t data = 0; + void* udata = nullptr; +}; +struct KernelEqueueEvent; +class IKernelEqueue; +using IKernelEqueue_t = IKernelEqueue*; +using KernelEvent_t = KernelEvent*; + +using trigger_func_t = void (*)(KernelEqueueEvent* event, void* trigger_data); +using reset_func_t = void (*)(KernelEqueueEvent* event); +using delete_func_t = void (*)(IKernelEqueue_t eq, KernelEqueueEvent* event); + +struct KernelFilter { + void* data = nullptr; + trigger_func_t trigger_func = nullptr; + reset_func_t reset_func = nullptr; + delete_func_t delete_event_func = nullptr; +}; + +struct KernelEqueueEvent { + bool triggered = false; + KernelEvent event; + KernelFilter filter; +}; + +} // namespace Kernel::EventQueue \ No newline at end of file diff --git a/core/kernel/filesystem.cpp b/core/kernel/filesystem.cpp new file mode 100644 index 00000000..4c46f7b4 --- /dev/null +++ b/core/kernel/filesystem.cpp @@ -0,0 +1,743 @@ +#define __APICALL_EXTERN +#include "filesystem.h" +#undef __APICALL_EXTERN + +#include "core/dmem/memoryManager.h" +#include "core/fileManager/fileManager.h" +#include "core/fileManager/types/type_file.h" +#include "core/fileManager/types/type_null.h" +#include "core/fileManager/types/type_random.h" +#include "core/fileManager/types/type_rng.h" +#include "core/fileManager/types/type_zero.h" +#include "core/memory/memory.h" +#include "logging.h" + +#include +#include +#include + +LOG_DEFINE_MODULE(filesystem); + +namespace { +std::pair convProtection(int prot) { + switch (prot & 0xf) { + case 0: return {PAGE_NOACCESS, 0}; + case 1: return {PAGE_READONLY, FILE_MAP_READ}; + case 2: + case 3: return {PAGE_READWRITE, FILE_MAP_ALL_ACCESS}; + case 4: return {PAGE_EXECUTE, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE}; + case 5: return {PAGE_EXECUTE_READ, FILE_MAP_EXECUTE | FILE_MAP_READ}; + case 6: + case 7: return {PAGE_EXECUTE_READWRITE, FILE_MAP_ALL_ACCESS}; + } + + return {PAGE_NOACCESS, 0}; +} + +std::unique_ptr createType_dev(std::filesystem::path path, std::ios_base::openmode mode) { + LOG_USE_MODULE(filesystem); + + if (path == "/dev/random" || path == "/dev/urandom") { + return createType_random(); + } else if (path == "/dev/rng") { + return createType_rng(); + } else if (path == "/dev/zero") { + return createType_zero(); + } else if (path == "/dev/null") { + return createType_null(); + } else { // todo: other devices + LOG_CRIT(L"%s: unknown device!", path.c_str()); + } + + return {}; +} + +int open_dev(const char* path, filesystem::SceOpen flags, filesystem::SceKernelMode kernelMode) { + LOG_USE_MODULE(filesystem); + + if (auto str = std::string_view(path); str.starts_with("/dev/std")) { + if (str.ends_with("stdin")) return 0; + if (str.ends_with("stdout")) return 1; + if (str.ends_with("stderr")) return 2; + return getErr(ErrCode::_ENOENT); + } + + std::ios_base::openmode mode = std::ios::binary; + + switch (flags.mode) { + case filesystem::SceOpenMode::RDONLY: mode |= std::ios::in; break; + case filesystem::SceOpenMode::WRONLY: mode |= std::ios::out; break; + case filesystem::SceOpenMode::RDWR: mode |= std::ios::out | std::ios::in; break; + } + + auto file = createType_dev(path, mode); + int const handle = accessFileManager().addFile(std::move(file), path); + LOG_INFO(L"OpenFile[%d]: %S mode:0x%x(0x%x)", handle, path, mode, kernelMode); + return handle; +} +} // namespace + +namespace filesystem { +int mmap(void* addr, size_t len, int prot, SceMap flags, int fd, int64_t offset, void** res) { + LOG_USE_MODULE(filesystem); + + // Mapping cases + if (flags.mode == SceMapMode::FIXED) { + LOG_ERR(L"todo: Mmap fixed 0x%08llx len:0x%08llx prot:%d flags:%d fd:%d offset:%lld", addr, len, prot, flags, fd, offset); + return getErr(ErrCode::_EINVAL); + } + + if (flags.mode == SceMapMode::VOID_) { + // reserve memory + LOG_ERR(L"todo: Mmap void 0x%08llx len:0x%08llx prot:%d flags:%d fd:%d offset:%lld", addr, len, prot, flags, fd, offset); + return getErr(ErrCode::_EINVAL); + } + + if (flags.type == SceMapType::ANON) { + int result = accessMemoryManager()->flexibleMemory()->map((uint64_t)addr, 0, len, prot, (int&)flags, 0, (uint64_t*)res); // registers mapping (flexible) + + LOG_DEBUG(L"Mmap anon addr:0x%08llx len:0x%08llx prot:%d flags:%d fd:%d offset:%lld -> out:0x%08llx", addr, len, prot, flags, fd, offset, *res); + return result; + } + // - + + // Do file mapping + if (fd < FILE_DESCRIPTOR_MIN) { + LOG_DEBUG(L"Mmap error addr:0x%08llx len:0x%08llx prot:%d flags:%08llx fd:%d offset:%lld -> out:0x%08llx", addr, len, prot, flags, fd, offset, *res); + return getErr(ErrCode::_EPERM); + } + + auto const [fileProt, viewProt] = convProtection(prot); + + auto file = accessFileManager().accessFile(fd); + + if (file->getNative() == nullptr) { + LOG_ERR(L"Mmap fd:%d no nativeHandle", fd); + return getErr(ErrCode::_EMFILE); + } + + uint64_t mappingLength = (fileProt == PAGE_READONLY) || (fileProt == PAGE_EXECUTE_READ) ? 0 : len; + auto lenSplit = std::bit_cast>(mappingLength); + + HANDLE hFileMapping = CreateFileMapping(file->getNative(), + NULL, // default security + fileProt, + lenSplit[1], // maximum object size (high-order DWORD) + lenSplit[0], // maximum object size (low-order DWORD) + NULL); // name of mapping object + + *res = nullptr; + + if (hFileMapping == NULL) { + LOG_ERR(L"Mmap CreateFileMapping == NULL for| 0x%08llx len:0x%08llx prot:%d flags:%0lx fd:%d offset:%lld err:%04x", addr, mappingLength, prot, flags, fd, + offset, GetLastError()); + } else { + auto offsetSplit = std::bit_cast>(offset); + *res = MapViewOfFile(hFileMapping, // handle to file mapping object + viewProt, // read/write permission + offsetSplit[1], // high offset + offsetSplit[0], // low offset + mappingLength); // number of bytes to map + if (*res == NULL) { + LOG_ERR(L"Mmap MapViewOfFile == NULL for| 0x%08llx len:0x%08llx prot:%d flags:%0lx fd:%d offset:%lld err:%04x", addr, mappingLength, prot, flags, fd, + offset, GetLastError()); + } else { + LOG_DEBUG(L"Mmap addr:0x%08llx len:0x%08llx prot:%d flags:%0lx fd:%d offset:%lld -> out:0x%08llx", addr, mappingLength, prot, flags, fd, offset, *res); + accessMemoryManager()->registerMapping((uint64_t)*res, len, MappingType::File); + } + } + + CloseHandle(hFileMapping); // is kept open internally until mapView is unmapped + + if (*res == nullptr) { + return getErr(ErrCode::_EACCES); + } + return Ok; +} + +int munmap(void* addr, size_t len) { + LOG_USE_MODULE(filesystem); + + // unregister and then delete + auto type = accessMemoryManager()->unregisterMapping((uint64_t)addr); + + switch (type) { + case MappingType::File: { + return UnmapViewOfFile(addr) != 0 ? Ok : -1; + } break; + + case MappingType::Direct: { + return accessMemoryManager()->directMemory()->unmap((uint64_t)addr, len); + } break; + + case MappingType::Flexible: { + return accessMemoryManager()->flexibleMemory()->unmap((uint64_t)addr, len); + } break; + + case MappingType::Fixed: { + LOG_ERR(L"tod munmap Fixed 0x%08llx 0x%08llx", addr, len); + } break; + + case MappingType::None: { + LOG_ERR(L"munmap unkown 0x%08llx 0x%08llx type:%d", addr, len, type); + } break; + } + return -1; +} + +size_t read(int handle, void* buf, size_t nbytes) { + LOG_USE_MODULE(filesystem); + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + return getErr(ErrCode::_EBADF); + } + + auto const count = file->read((char*)buf, nbytes); + LOG_TRACE(L"KernelRead[%d]: 0x%08llx:%llu read(%lld)", handle, (uint64_t)buf, nbytes, count); + return count; +} + +int64_t write(int handle, const void* buf, size_t nbytes) { + LOG_USE_MODULE(filesystem); + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"KernelWrite[%d] file==nullptr: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes); + return getErr(ErrCode::_EBADF); + } + + size_t const count = file->write((char*)buf, nbytes); + + LOG_TRACE(L"KernelWrite[%d]: 0x%08llx:%llu count:%llu", handle, (uint64_t)buf, nbytes, count); + + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + return count; +} + +int ioctl(int handle, int request, SceVariadicList argp) { + LOG_USE_MODULE(filesystem); + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"KernelIoctl[%d] file==nullptr: request:0x%08llx argp:0x%08llx", handle, request, (uint64_t)argp); + return getErr(ErrCode::_EBADF); + } + + int const ret = file->ioctl(request, argp); + + LOG_TRACE(L"KernelIoctl[%d] request:0x%08llx argp:0x%08llx, ret:%d", handle, request, (uint64_t)argp, ret); + + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + + return ret; +} + +int open(const char* path, SceOpen flags, SceKernelMode kernelMode) { + LOG_USE_MODULE(filesystem); + + if (path == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + if (std::string_view(path).starts_with("/dev/")) { + return open_dev(path, flags, kernelMode); + } + + auto mapped = accessFileManager().getMappedPath(path); + if (!mapped) { + return getErr(ErrCode::_EACCES); + } + auto const mappedPath = mapped.value(); + + // handle excl (file already exists) + if (flags.excl && flags.create && std::filesystem::exists(mappedPath)) { + return getErr(ErrCode::_EEXIST); + } + // - + + bool isDir = std::filesystem::is_directory(mappedPath); + + if (flags.directory || isDir) { + if (!isDir) { + LOG_WARN(L"Directory doesn't exist: %s", mappedPath.c_str()); + return getErr(ErrCode::_ENOTDIR); + } + + if (!std::filesystem::exists(mappedPath.parent_path())) std::filesystem::create_directories(mappedPath); + + auto dirIt = std::make_unique(mappedPath); + int const handle = accessFileManager().addDirIterator(std::move(dirIt), mappedPath); + LOG_INFO(L"OpenDir [%d]: %S (%s)", handle, path, mappedPath.c_str()); + + return handle; + } else { + // error if read only input file does not exist + if (flags.mode == SceOpenMode::RDONLY && !std::filesystem::exists(mappedPath)) { + LOG_WARN(L"File doesn't exist: %s mode:0x%x", mappedPath.c_str(), flags.mode); + return getErr(ErrCode::_ENOENT); + } + + auto file = createType_file(mappedPath, flags); + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + int const handle = accessFileManager().addFile(std::move(file), mappedPath); + LOG_INFO(L"OpenFile[%d]: %s mode:0x%x(0x%x)", handle, mappedPath.c_str(), *(int*)&flags, kernelMode); + + return handle; + } +} + +int close(int handle) { + LOG_USE_MODULE(filesystem); + LOG_INFO(L"Closed[%d]", handle); + if (handle < FILE_DESCRIPTOR_MIN) { + return getErr(ErrCode::_EPERM); + } + + accessFileManager().remove(handle); + return Ok; +} + +int unlink(const char* path) { + LOG_USE_MODULE(filesystem); + if (path == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto const _mapped = accessFileManager().getMappedPath(path); + if (!_mapped) { + return getErr(ErrCode::_EACCES); + } + auto const mapped = _mapped.value(); + + if (std::filesystem::is_directory(mapped)) { + return getErr(ErrCode::_EPERM); + } + + if (std::filesystem::remove(mapped)) { + LOG_INFO(L"Deleted: %S", path); + return Ok; + } + + return getErr(ErrCode::_ENOENT); +} + +int chmod(const char* path, SceKernelMode mode) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"TODO %S", __FUNCTION__); + return Ok; +} + +int checkReachability(const char* path) { + auto mapped = accessFileManager().getMappedPath(path); + if (!mapped) { + return getErr(ErrCode::_EACCES); + } + auto const mappedPath = mapped.value(); + + if (std::filesystem::exists(mappedPath.parent_path())) return Ok; + + return getErr(ErrCode::_EACCES); +} + +void sync(void) { + // todo: sync all open files? +} + +int fsync(int handle) { + LOG_USE_MODULE(filesystem); + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"KernelFsync[%d]", handle); + return getErr(ErrCode::_EBADF); + } + + file->sync(); + return Ok; +} + +int fdatasync(int fd) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"todo %S %d", __FUNCTION__, fd); + return Ok; +} + +int fcntl(int handle, int cmd, SceVariadicList argp) { + LOG_USE_MODULE(filesystem); + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"KernelFcntl[%d] file==nullptr: cmd:0x%08lx argp:0x%08llx", handle, cmd, (uint64_t)argp); + return getErr(ErrCode::_EBADF); + } + + int const ret = file->fcntl(cmd, argp); + + LOG_TRACE(L"KernelFcntl[%d] request:0x%08llx argp:0x%08lx, ret:%d", handle, cmd, (uint64_t)argp, ret); + + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + + return ret; +} + +size_t readv(int handle, const SceKernelIovec* iov, int iovcnt) { + LOG_USE_MODULE(filesystem); + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + return getErr(ErrCode::_EBADF); + } + + // todo: move it to IFile? + size_t count = 0; + + for (int n = 0; n < iovcnt; ++n) { + auto* item = &iov[n]; + + if (item->iov_base == nullptr || item->iov_len == 0) continue; + + auto const countTemp = file->read((char*)item->iov_base, item->iov_len); + + LOG_TRACE(L"KernelRead[%d]: 0x%08llx:%llu read(%lld)", handle, (uint64_t)item->iov_base, item->iov_len, countTemp); + + count += countTemp; + if (auto err = file->getErr(); err != Ok) { + LOG_TRACE(L"file end"); + break; + } + } + + return count; +} + +size_t writev(int handle, const SceKernelIovec* iov, int iovcnt) { + LOG_USE_MODULE(filesystem); + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + return getErr(ErrCode::_EBADF); + } + + // todo: move it to IFile? + size_t count = 0; + + for (int n = 0; n < iovcnt; n++) { + auto* item = &iov[n]; + + if (item->iov_base == nullptr || item->iov_len == 0) continue; + + auto const countTemp = file->write(item->iov_base, item->iov_len); + + LOG_TRACE(L"KernelWrite[%d]: 0x%08llx:%llu write(%lld)", handle, (uint64_t)item->iov_base, item->iov_len, countTemp); + + count += countTemp; + + if (auto err = file->getErr(); err != Ok) { + LOG_TRACE(L"file end"); + break; + } + } + + return count; +} + +int fchmod(int fd, SceKernelMode mode) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int rename(const char* from, const char* to) { + auto mapped1 = accessFileManager().getMappedPath(from); + if (!mapped1) { + return getErr(ErrCode::_EACCES); + } + auto mapped2 = accessFileManager().getMappedPath(to); + if (!mapped2) { + return getErr(ErrCode::_EACCES); + } + try { + std::filesystem::rename(*mapped1, *mapped2); + } catch (...) { + return getErr(ErrCode::_ENFILE); + } + return Ok; +} + +int mkdir(const char* path, SceKernelMode mode) { + auto _mapped = accessFileManager().getMappedPath(path); + if (!_mapped) { + return getErr(ErrCode::_EACCES); + } + auto const mapped = _mapped.value(); + + if (!std::filesystem::create_directory(mapped)) { + return getErr(ErrCode::_EIO); + } + + return Ok; +} + +int rmdir(const char* path) { + auto mapped = accessFileManager().getMappedPath(path); + if (!mapped) { + return getErr(ErrCode::_EACCES); + } + std::filesystem::remove_all(*mapped); + return Ok; +} + +int utimes(const char* path, const SceKernelTimeval* times) { + return Ok; +} + +int stat(const char* path, SceKernelStat* sb) { + LOG_USE_MODULE(filesystem); + + LOG_TRACE(L"KernelStat: %S", path); + + memset(sb, 0, sizeof(SceKernelStat)); + auto _mapped = accessFileManager().getMappedPath(path); + if (!_mapped) { + return getErr(ErrCode::_EACCES); + } + auto const mapped = _mapped.value(); + + if (!std::filesystem::exists(mapped)) { + return getErr(ErrCode::_ENOENT); + } + + bool const isDir = std::filesystem::is_directory(mapped); + sb->mode = 0000777u | (isDir ? 0040000u : 0100000u); + + if (isDir) { + sb->size = 0; + sb->blksize = 512; + sb->blocks = 0; + } else { + sb->size = (int64_t)std::filesystem::file_size(mapped); + sb->blksize = 512; + sb->blocks = (sb->size + 511) / 512; + + auto const lastW = std::filesystem::last_write_time(mapped); + + auto const time = std::chrono::time_point_cast(lastW).time_since_epoch().count(); + + ns2timespec(&sb->aTime, time); + ns2timespec(&sb->mTime, time); + } + + sb->cTime = sb->aTime; + sb->birthtime = sb->mTime; + + return Ok; +} + +int fstat(int fd, SceKernelStat* sb) { + auto mapped = accessFileManager().getPath(fd); + if (mapped.empty()) { + return getErr(ErrCode::_EACCES); + } + return stat(mapped.string().c_str(), sb); +} + +int futimes(int fd, const SceKernelTimeval* times) { + return Ok; +} + +int getdirentries(int fd, char* buf, int nbytes, long* basep) { + if (fd < FILE_DESCRIPTOR_MIN) { + return getErr(ErrCode::_EPERM); + } + if (buf == nullptr) { + return getErr(ErrCode::_EFAULT); + } + + auto count = accessFileManager().getDents(fd, buf, nbytes, (int64_t*)basep); + if (count < 0) { + return getErr(ErrCode::_EINVAL); + } + + return count; +} + +int getdents(int fd, char* buf, int nbytes) { + return getdirentries(fd, buf, nbytes, nullptr); +} + +size_t preadv(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset) { + LOG_USE_MODULE(filesystem); + LOG_TRACE(L"preadv [%d]", handle); + + if (offset < 0) { + return getErr(ErrCode::_EINVAL); + } + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"preadv[%d] file==nullptr", handle); + return getErr(ErrCode::_EBADF); + } + + file->clearError(); + file->lseek(offset, SceWhence::beg); + + auto const count = readv(handle, iov, iovcnt); + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + + return count; +} + +size_t pwritev(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset) { + LOG_USE_MODULE(filesystem); + LOG_TRACE(L"pwritev [%d]", handle); + + if (offset < 0) { + return getErr(ErrCode::_EINVAL); + } + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"pwritev[%d] file==nullptr", handle); + return getErr(ErrCode::_EBADF); + } + + file->clearError(); + file->lseek(offset, SceWhence::beg); + + auto const count = writev(handle, iov, iovcnt); + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + + return count; +} + +size_t pread(int handle, void* buf, size_t nbytes, int64_t offset) { + LOG_USE_MODULE(filesystem); + LOG_TRACE(L"pread [%d]", handle); + + if (buf == nullptr) { + return getErr(ErrCode::_EFAULT); + } + + if (offset < 0) { + return getErr(ErrCode::_EINVAL); + } + + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"pread[%d] file==nullptr: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes); + return getErr(ErrCode::_EBADF); + } + + file->clearError(); + file->lseek(offset, SceWhence::beg); + + auto const count = file->read((char*)buf, nbytes); + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + + LOG_TRACE(L"pread[%d]: 0x%08llx:%llu read(%lld) offset:0x%08llx", handle, (uint64_t)buf, nbytes, count, offset); + return count; +} + +size_t pwrite(int handle, const void* buf, size_t nbytes, int64_t offset) { + LOG_USE_MODULE(filesystem); + LOG_TRACE(L"pwrite[%d]: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes); + auto file = accessFileManager().accessFile(handle); + if (file == nullptr) { + LOG_ERR(L"write[%d] file==nullptr: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes); + return getErr(ErrCode::_EBADF); + } + + file->clearError(); + file->lseek(offset, SceWhence::beg); + + auto const count = file->write((char*)buf, nbytes); + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + + return count; +} + +int64_t lseek(int handle, int64_t offset, int whence) { + LOG_USE_MODULE(filesystem); + LOG_TRACE(L"lseek [%d] 0x%08llx %d", handle, offset, whence); + + if (whence > 2) return getErr(ErrCode::_EINVAL); + + auto file = accessFileManager().accessFile(handle); + file->clearError(); + + auto const pos = file->lseek(offset, (SceWhence)whence); + + if (auto err = file->getErr(); err != Ok) { + return getErr((ErrCode)err); + } + return pos; +} + +int truncate(const char* path, int64_t length) { + auto mapped = accessFileManager().getMappedPath(path); + if (!mapped) { + return getErr(ErrCode::_EACCES); + } + std::filesystem::resize_file(*mapped, length); + return Ok; +} + +int ftruncate(int fd, int64_t length) { + auto mapped = accessFileManager().getPath(fd); + if (mapped.empty()) { + return getErr(ErrCode::_EACCES); + } + return truncate(mapped.string().c_str(), length); +} + +int setCompressionAttribute(int fd, int flag) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int lwfsSetAttribute(int fd, int flags) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int lwfsAllocateBlock(int fd, int64_t size) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int lwfsTrimBlock(int fd, int64_t size) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int64_t lwfsLseek(int fd, int64_t offset, int whence) { + LOG_USE_MODULE(filesystem); + LOG_ERR(L"seek [%d]", fd); + return Ok; +} + +size_t lwfsWrite(int fd, const void* buf, size_t nbytes) { + return Ok; +} +} // namespace filesystem diff --git a/core/kernel/filesystem.h b/core/kernel/filesystem.h new file mode 100644 index 00000000..0c40da4c --- /dev/null +++ b/core/kernel/filesystem.h @@ -0,0 +1,138 @@ +#pragma once + +#include "modules_include/common.h" + +#include + +namespace filesystem { + +enum class SceMapMode : uint16_t { + SHARED = 0x0001, + PRIVATE = 0x0002, + FIXED = 0x0010, + NO_OVERWRITE = 0x0080, + VOID_ = 0x0100, +}; + +enum class SceMapType : uint16_t { + FILE = 0x0, + ANON = 0x1, + SYSTEM = 0x2, + ALLAVAILABLE = 0x4, +}; + +struct SceMap { + SceMapMode mode : 12; + SceMapType type : 4; +}; + +enum class SceOpenMode : uint32_t { + RDONLY = 0, + WRONLY = 1, + RDWR = 2, +}; + +struct SceOpen { + SceOpenMode mode : 2; + uint32_t nonblock : 1; + uint32_t append : 1; + uint32_t shlock : 1; + uint32_t exlock : 1; + uint32_t async : 1; + uint32_t fsync : 1; + uint32_t nofollow : 1; + uint32_t create : 1; + uint32_t trunc : 1; + uint32_t excl : 1; + uint32_t dsync : 1; + uint32_t dummy0 : 1; + uint32_t fhaslock : 1; + uint32_t noctty : 1; + uint32_t direct : 1; + uint32_t directory : 1; + uint32_t exec : 1; + uint32_t tty_init : 1; + uint32_t cloexec : 1; +}; + +struct SceKernelStat { + uint32_t dev; + uint32_t ino; + uint16_t mode; + uint16_t nlink; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + SceKernelTimespec aTime; + SceKernelTimespec mTime; + SceKernelTimespec cTime; + int64_t size; + int64_t blocks; + uint32_t blksize; + uint32_t flags; + uint32_t gen; + int32_t lspare; + SceKernelTimespec birthtime; + unsigned int: (8 / 2) * (16 - static_cast(sizeof(SceKernelTimespec))); + unsigned int: (8 / 2) * (16 - static_cast(sizeof(SceKernelTimespec))); +}; + +struct Sce_iovec { + void* iov_base; + size_t iov_len; +}; + +typedef uint16_t SceKernelMode; // todo needed? + +typedef struct Sce_iovec SceKernelIovec; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL int mmap(void* addr, size_t len, int prot, SceMap flags, int fd, int64_t offset, void** res); +__APICALL int munmap(void* address, size_t len); +__APICALL size_t read(int handle, void* buf, size_t nbytes); +__APICALL int ioctl(int handle, int request, SceVariadicList argp); +__APICALL int64_t write(int handle, const void* buf, size_t nbytes); +__APICALL int open(const char* path, SceOpen flags, SceKernelMode kernelMode); +__APICALL int close(int handle); +__APICALL int unlink(const char* path); +__APICALL int chmod(const char* path, SceKernelMode mode); +__APICALL int checkReachability(const char* path); +__APICALL void sync(void); +__APICALL int fsync(int handle); +__APICALL int fdatasync(int fd); +__APICALL int fcntl(int fd, int cmd, SceVariadicList argp); +__APICALL size_t readv(int handle, const SceKernelIovec* iov, int iovcnt); +__APICALL size_t writev(int handle, const SceKernelIovec* iov, int iovcnt); +__APICALL int fchmod(int fd, SceKernelMode mode); +__APICALL int rename(const char* from, const char* to); +__APICALL int mkdir(const char* path, SceKernelMode mode); +__APICALL int rmdir(const char* path); +__APICALL int utimes(const char* path, const SceKernelTimeval* times); +__APICALL int stat(const char* path, SceKernelStat* sb); +__APICALL int fstat(int fd, SceKernelStat* sb); +__APICALL int futimes(int fd, const SceKernelTimeval* times); +__APICALL int getdirentries(int fd, char* buf, int nbytes, long* basep); +__APICALL int getdents(int fd, char* buf, int nbytes); +__APICALL size_t preadv(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset); +__APICALL size_t pwritev(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset); +__APICALL size_t pread(int handle, void* buf, size_t nbytes, int64_t offset); +__APICALL size_t pwrite(int handle, const void* buf, size_t nbytes, int64_t offset); +__APICALL int64_t lseek(int handle, int64_t offset, int whence); +__APICALL int truncate(const char* path, int64_t length); +__APICALL int ftruncate(int fd, int64_t length); +__APICALL int setCompressionAttribute(int fd, int flag); +__APICALL int lwfsSetAttribute(int fd, int flags); +__APICALL int lwfsAllocateBlock(int fd, int64_t size); +__APICALL int lwfsTrimBlock(int fd, int64_t size); +__APICALL int64_t lwfsLseek(int fd, int64_t offset, int whence); +__APICALL size_t lwfsWrite(int fd, const void* buf, size_t nbytes); + +#undef __APICALL +}; // namespace filesystem diff --git a/core/kernel/pthread.cpp b/core/kernel/pthread.cpp new file mode 100644 index 00000000..7143121a --- /dev/null +++ b/core/kernel/pthread.cpp @@ -0,0 +1,1296 @@ + +#define __APICALL_PTHREAD_EXTERN +#include "pthread.h" +#undef __APICALL_PTHREAD_EXTERN + +#include "logging.h" +#include "modules_include/common.h" +#include "pthread_intern.h" + +#define __APICALL_IMPORT +#include "core/dmem/memoryManager.h" +#include "core/fileManager/fileManager.h" +#include "core/runtime/runtimeLinker.h" +#include "core/timer/timer.h" +#undef __APICALL_IMPORT + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_DEFINE_MODULE(pthread); + +namespace { +NT_TIB* getTIB() { +#ifdef _M_IX86 + return (NT_TIB*)__readfsdword(0x18); +#elif _M_AMD64 + return (NT_TIB*)__readgsqword(0x30); +#else +#error unsupported architecture +#endif +} + +struct PImpl { + boost::mutex mutex; + PImpl() = default; +}; + +PImpl* getData() { + static PImpl data; + return &data; +} + +void setThreadPrio(boost::thread::native_handle_type nativeH, int prio) { + int pr = 0; + if (prio <= 478) { + int pr = 0; + pr = +2; + } else if (prio >= 733) { + pr = -2; + } + + SetThreadPriority(nativeH, pr); +} + +auto getTimeDuration(SceKernelTimespec const* t) { + auto now = boost::chrono::high_resolution_clock::now(); + SceKernelTimespec tp; + tp.tv_sec = boost::chrono::time_point_cast(now).time_since_epoch().count(); + tp.tv_nsec = boost::chrono::time_point_cast(now).time_since_epoch().count() % 1000000000; + + auto diffSec = t->tv_sec - tp.tv_sec; + auto diffNsec = t->tv_nsec - tp.tv_nsec; + return boost::chrono::duration(boost::chrono::seconds(diffSec) + boost::chrono::nanoseconds(diffNsec)); +} + +static std::atomic g_cancelState = SceCancelState::DISABLE; +static std::atomic g_cancelType = SceCancelType::DEFERRED; + +auto getThreadDtors() { + static thread_dtors_func_t obj = nullptr; + return &obj; +} + +ScePthread getPthread(ScePthread_obj obj) { + uint64_t pthreadOffset = ((uint64_t const*)obj)[0]; + return (ScePthread)&obj[pthreadOffset]; +} + +int threadCounter() { + static int counter = 0; + return ++counter; +} + +size_t mutexCounter() { + static int counter = 0; + return ++counter; +} + +void initTLS(ScePthread_obj obj) { + auto pthread = getPthread(obj); + pthread->dtv[0] = 0; + std::fill(&pthread->dtv[1], &pthread->dtv[DTV_SIZE - 2], 0); // skip mainprog + pthread->dtv[DTV_SIZE - 1] = XSAVE_CHK_GUARD; + accessRuntimeLinker().initTLS(obj); // reset tls +} + +void init_pThread() { + auto pimpl = getData(); +} + +const char* sanThreadName(const char* name) { + MEMORY_BASIC_INFORMATION mbi = {}; + if (::VirtualQuery(name, &mbi, sizeof(mbi))) return name; + return "[BADPTR]"; +} +} // namespace + +namespace pthread { +int attrDestroy(ScePthreadAttr* attr) { + delete *attr; + *attr = nullptr; + return Ok; +} + +int attrGetstack(const ScePthreadAttr* attr, void** stackAddr, size_t* stackSize) { + if (attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + if (stackAddr != nullptr) *stackAddr = (*attr)->getStackAddr(); + if (stackSize != nullptr) *stackSize = (*attr)->getStackSize(); + return Ok; +} + +int attrGetstacksize(const ScePthreadAttr* attr, size_t* stackSize) { + if (stackSize == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *stackSize = (*attr)->getStackSize(); + return Ok; +} + +int attrGetguardsize(const ScePthreadAttr* attr, size_t* guardSize) { + if (guardSize == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *guardSize = (*attr)->getGuardSize(); + return Ok; +} + +int attrGetstackaddr(const ScePthreadAttr* attr, void** stackAddr) { + if (stackAddr == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + *stackAddr = (*attr)->getStackAddr(); + return Ok; +} + +int attrGetdetachstate(const ScePthreadAttr* attr, SceDetachState* state) { + if (state == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *state = (*attr)->getDetachState(); + return Ok; +} + +int attrSetstacksize(ScePthreadAttr* attr, size_t stackSize) { + if (stackSize == 0 || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setStackSize(stackSize); + return Ok; +} + +int attrSetguardsize(ScePthreadAttr* attr, size_t guardSize) { + if (attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setGuardSize(guardSize); + return Ok; +} + +int attrSetstack(ScePthreadAttr* attr, void* addr, size_t size) { + LOG_USE_MODULE(pthread); + LOG_DEBUG(L"Thread Stack 0x%08llx addr:0x%08llx size:0x%08llx", (uint64_t)attr, addr, size); + + if (addr == nullptr || size == 0 || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setStackAddr(addr); + (*attr)->setStackSize(size); + + return Ok; +} + +int attrSetstackaddr(ScePthreadAttr* attr, void* addr) { + if (addr == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + (*attr)->setStackAddr(addr); + return Ok; +} + +int attrSetdetachstate(ScePthreadAttr* attr, SceDetachState state) { + if (attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setDetachState(state); + return Ok; +} + +int barrierDestroy(ScePthreadBarrier* barrier) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int barrierInit(ScePthreadBarrier* barrier, const ScePthreadBarrierattr* attr, unsigned count, const char* name) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int barrierWait(ScePthreadBarrier* barrier) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int barrierattrDestroy(ScePthreadBarrierattr* barrier) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int barrierattrInit(ScePthreadBarrierattr* barrier) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"todo %S", __FUNCTION__); + return Ok; +} + +int barrierattrGetpshared(ScePthreadBarrierattr* barrier, int* pshared) { + if (barrier != nullptr) { + *pshared = (*barrier)->pshared; + } else { + return getErr(ErrCode::_EACCES); + } + return Ok; +} + +int barrierattrSetpshared(ScePthreadBarrierattr* barrier, int pshared) { + if (barrier != nullptr) { + (*barrier)->pshared = pshared; + } else { + return getErr(ErrCode::_EACCES); + } + return Ok; +} + +int condattrDestroy(ScePthreadCondattr* attr) { + delete *attr; + *attr = nullptr; + return Ok; +} + +int condattrInit(ScePthreadCondattr* attr) { + *attr = new PthreadCondattrPrivate(); + return Ok; +} + +auto condInit_intern(const char* name) { + auto cond = new PthreadCondPrivate(); + if (name != nullptr) cond->name = name; + return cond; +} + +int condInit(ScePthreadCond* cond, const ScePthreadCondattr* attr, const char* name) { + if (cond == nullptr) { + return getErr(ErrCode::_EINVAL); + } + *cond = condInit_intern(name); + + return Ok; +} + +static int checkCondInit(ScePthreadCond* cond) { + if (*cond == nullptr) { + static boost::mutex mutexInt; + // Is Static Object -> init + // race condition on first init -> protect + boost::unique_lock lock(mutexInt); + if (*cond == nullptr) *cond = condInit_intern(nullptr); + } + return Ok; +} + +int condBroadcast(ScePthreadCond* cond) { + if (int res = checkCondInit(cond); res != Ok) return res; + + (*cond)->p.notify_all(); + LOG_USE_MODULE(pthread); + // LOG_DEBUG(L"Broadcast: %S", (*cond)->name.c_str()); + return Ok; +} + +int condDestroy(ScePthreadCond* cond) { + if (cond == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + delete *cond; + *cond = nullptr; + return Ok; +} + +int condSignal(ScePthreadCond* cond) { + if (int res = checkCondInit(cond); res != Ok) return res; + LOG_USE_MODULE(pthread); + // LOG_DEBUG(L"Cond: %S", (*cond)->name.c_str()); + (*cond)->p.notify_one(); + return Ok; +} + +int condSignalto(ScePthreadCond* cond, ScePthread_obj obj) { + if (int res = checkCondInit(cond); res != Ok) return res; + + (*cond)->p.notify_all(); + return Ok; +} + +int condTimedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, SceKernelUseconds usec) { + if (int res = checkCondInit(cond); res != Ok) return res; + + LOG_USE_MODULE(pthread); + // LOG_DEBUG(L"->Cond: %S mutex:%d", (*cond)->name.c_str(), (*mutex)->id); + + auto ret = (*cond)->p.do_wait_until((*mutex)->p, boost::detail::internal_platform_clock::now() + boost::chrono::microseconds(usec)) + ? Ok + : getErr(ErrCode::_ETIMEDOUT); + // LOG_DEBUG(L"<-Cond: %S", (*cond)->name.c_str()); + return ret; +} + +int condTimedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, const SceKernelTimespec* t) { + if (int res = checkCondInit(cond); res != Ok) return res; + + LOG_USE_MODULE(pthread); + // LOG_DEBUG(L"->Cond: %S mutex:%d", (*cond)->name.c_str(), (*mutex)->id); + + auto ret = (*cond)->p.do_wait_until((*mutex)->p, boost::detail::internal_platform_clock::now() + getTimeDuration(t)) ? Ok : getErr(ErrCode::_ETIMEDOUT); + // LOG_DEBUG(L"<-Cond: %S", (*cond)->name.c_str()); + return ret; +} + +int condWait(ScePthreadCond* cond, ScePthreadMutex* mutex) { + if (int res = checkCondInit(cond); res != Ok) return res; + + LOG_USE_MODULE(pthread); + + if ((*mutex)->p.recursion_count < 1) return getErr(ErrCode::_EPERM); + + // LOG_DEBUG(L"->Cond: %S mutex:%d", (*cond)->name.c_str(), (*mutex)->id); + (*cond)->p.do_wait_until((*mutex)->p, boost::detail::internal_platform_timepoint::getMax()); + + // LOG_DEBUG(L"<-Cond: %S", (*cond)->name.c_str()); + return Ok; +} + +int detach(ScePthread_obj obj) { + if (obj == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto thread = getPthread(obj); + + LOG_USE_MODULE(pthread); + LOG_INFO(L"Detach :%d", thread->unique_id); + thread->detached = true; + // thread->p.detach(); // Signaling (raiseSignal) doesnt work when detached + return Ok; +} + +int equal(ScePthread_obj thread1, ScePthread_obj thread2) { + if (thread1 == nullptr || thread2 == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto t1 = getPthread(thread1); + auto t2 = getPthread(thread2); + return t1->p == t2->p; +} + +int getcpuclockid(ScePthread_obj thread, int* clock) { + return 0; // todo +} + +int join(ScePthread_obj obj, void** value) { + if (obj == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto thread = getPthread(obj); + thread->p.join(); + + if (!thread->detached) { + LOG_USE_MODULE(pthread); + LOG_DEBUG(L"Delete thread:%d", thread->unique_id); + // Cleanup thread + thread->~PthreadPrivate(); + delete[] obj; + } + // - + return Ok; +} + +int keyDelete(ScePthreadKey key) { + accessRuntimeLinker().deleteTLSKey(key); + return Ok; +} + +int keyCreate(ScePthreadKey* key, pthread_key_destructor_func_t destructor) { + if (key == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *key = accessRuntimeLinker().createTLSKey((void*)destructor); + if (*key < 0) { + return getErr(ErrCode::_EAGAIN); + } + + return Ok; +} + +int mutexattrInit(ScePthreadMutexattr* attr) { + LOG_USE_MODULE(pthread); + + *attr = new PthreadMutexattrPrivate(); + // LOG_TRACE(L"->mutex_attr 0x%08llx", (uint64_t)attr); + return Ok; +} + +int mutexattrDestroy(ScePthreadMutexattr* attr) { + if (attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + // LOG_USE_MODULE(pthread); + // LOG_TRACE(L"<-mutex_attr 0x%08llx", (uint64_t)attr); + delete *attr; + *attr = nullptr; + + return Ok; +} + +int mutexattrGettype(ScePthreadMutexattr* attr, int* type) { + if (attr == nullptr) return getErr(ErrCode::_EINVAL); + *type = (int)((*attr)->type); + return Ok; +} + +int mutexattrSettype(ScePthreadMutexattr* attr, int type) { + if (attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + // LOG_USE_MODULE(pthread); + // LOG_TRACE(L"attr 0x%08llx type:%d", (uint64_t)attr, type); + + (*attr)->type = (SceMutexType)type; + return Ok; +} + +int mutexDestroy(ScePthreadMutex* mutex) { + if (mutex == nullptr) { + return getErr(ErrCode::_EINVAL); + } + delete *mutex; + *mutex = nullptr; + + return Ok; +} + +auto mutexInit_intern(const ScePthreadMutexattr* attr) { + auto mutex = std::make_unique().release(); + if (attr != nullptr && *attr != nullptr) mutex->type = (*attr)->type; + mutex->id = mutexCounter(); + LOG_USE_MODULE(pthread); + // LOG_DEBUG(L"mutex ini| id:%llu type:%d", mutex->id, (int)mutex->type); + return mutex; +} + +int mutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, const char* name) { + + if (mutex == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *mutex = mutexInit_intern(attr); + return Ok; +} + +static int checkMutexInit(ScePthreadMutex* mutex) { + if (mutex == nullptr) return getErr(ErrCode::_EINVAL); + if (*mutex == nullptr) { + static boost::mutex mutexInt; + // Is Static Object -> init + // race condition on first init -> protect + boost::unique_lock lock(mutexInt); + if (*mutex == nullptr) *mutex = mutexInit_intern(nullptr); + } + return Ok; +} + +int mutexLock(ScePthreadMutex* mutex) { + LOG_USE_MODULE(pthread); + if (int res = checkMutexInit(mutex); res != Ok) return res; + + // LOG_DEBUG(L"-> mutex lock(%S)| id:%llu thread:%d", (*mutex)->name.c_str(), (*mutex)->id, getThreadId()); + (*mutex)->p.lock(); + int ret = Ok; + if ((*mutex)->type != SceMutexType::RECURSIVE && (*mutex)->p.recursion_count > 1) { + ret = getErr(ErrCode::_EDEADLK); + (*mutex)->p.unlock(); + } + // LOG_DEBUG(L"<- mutex lock(%S)| id:%llu result:%d", (*mutex)->name.c_str(), (*mutex)->id, ret); + + return ret; +} + +int mutexTrylock(ScePthreadMutex* mutex) { + if (int res = checkMutexInit(mutex); res != Ok) return res; + + int ret = (*mutex)->p.try_lock(); + if (ret > 0) { + if ((*mutex)->type != SceMutexType::RECURSIVE && (*mutex)->p.recursion_count > 1) { + ret = getErr(ErrCode::_EDEADLK); + (*mutex)->p.unlock(); + } + return Ok; + } + + return getErr(ErrCode::_EBUSY); +} + +int mutexTimedlock(ScePthreadMutex* mutex, SceKernelUseconds usec) { + LOG_USE_MODULE(pthread); + if (int res = checkMutexInit(mutex); res != Ok) return res; + + int ret = (*mutex)->p.try_lock_for(boost::chrono::microseconds(usec)); + if (ret > 0) { + if ((*mutex)->type != SceMutexType::RECURSIVE && (*mutex)->p.recursion_count > 1) { + ret = getErr(ErrCode::_EDEADLK); + (*mutex)->p.unlock(); + } + return Ok; + } + return getErr(ErrCode::_ETIMEDOUT); +} + +int mutexTimedlock(ScePthreadMutex* mutex, const SceKernelTimespec* t) { + LOG_USE_MODULE(pthread); + if (int res = checkMutexInit(mutex); res != Ok) return res; + + boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(35000); + // LOG_TRACE(L"-> mutex timed lock(%S)| id:%llu", (*mutex)->name.c_str(), (*mutex)->id); + + int ret = (*mutex)->p.try_lock_for(getTimeDuration(t)); + if (ret > 0) { + if ((*mutex)->type != SceMutexType::RECURSIVE && (*mutex)->p.recursion_count > 1) { + ret = getErr(ErrCode::_EDEADLK); + (*mutex)->p.unlock(); + } + return Ok; + } + return getErr(ErrCode::_ETIMEDOUT); + + // LOG_TRACE(L"<- mutex timed lock(%S)| id:%llu result:%d", (*mutex)->name.c_str(), (*mutex)->id, result); + + // LOG_TRACE(L"mutex unlock(%S)| id:%llu result:%d", (*mutex)->name.c_str(), (*mutex)->id, result); +} + +int mutexUnlock(ScePthreadMutex* mutex) { + + LOG_USE_MODULE(pthread); + if (*mutex == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + // LOG_DEBUG(L"-> mutex unlock(%S)| id:%llu thread:%d", (*mutex)->name.c_str(), (*mutex)->id, getThreadId()); + int ret = Ok; + if ((*mutex)->p.locking_thread_id != boost::winapi::GetCurrentThreadId()) { + ret = getErr(ErrCode::_EPERM); + } else { + (*mutex)->p.unlock(); + } + // LOG_DEBUG(L"<- mutex unlock(%S)| id:%llu result:%d", (*mutex)->name.c_str(), (*mutex)->id, ret); + return ret; +} + +int rwlockDestroy(ScePthreadRwlock* rwlock) { + if (rwlock == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + // LOG_INFO(L"<-- rwlock: %S, %d", (*rwlock)->name.c_str(), result); + + delete *rwlock; + *rwlock = nullptr; + + return Ok; +} + +auto createRwLock_intern(const char* name) { + auto rwlock = std::make_unique().release(); + rwlock->id = mutexCounter(); + if (name != nullptr) rwlock->name = name; + return rwlock; +} + +int rwlockInit(ScePthreadRwlock* rwlock, const ScePthreadRwlockattr* attr, const char* name) { + if (rwlock == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *rwlock = createRwLock_intern(name); + + // LOG_DEBUG(L"-> rwlock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id, result); + return Ok; +} + +static int checkRwLockInit(ScePthreadRwlock* rwlock) { + if (*rwlock == nullptr) { + // Is Static Object -> init + static boost::mutex mutexInt; + // Is Static Object -> init + // race condition on first init -> protect + boost::unique_lock lock(mutexInt); + if (*rwlock == nullptr) *rwlock = createRwLock_intern(nullptr); + } + return Ok; +} + +int rwlockRdlock(ScePthreadRwlock* rwlock) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + + try { + (*rwlock)->p.lock_shared(); + } catch (...) { + // LOG_TRACE(L"<- rdlock lock(%S)| id:%llu result:EAGAIN", (*rwlock)->name.c_str(), (*rwlock)->id); + return getErr(ErrCode::_EAGAIN); + } + + // LOG_TRACE(L"<- rdlock lock(%S)| id:%llu", (*rwlock)->name.c_str(), (*rwlock)->id); + return Ok; +} + +int rwlockTimedrdlock(ScePthreadRwlock* rwlock, SceKernelUseconds usec) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + auto ret = (*rwlock)->p.timed_lock_shared(boost::chrono::duration(boost::chrono::microseconds(usec))) ? Ok : getErr(ErrCode::_ETIMEDOUT); + + // LOG_TRACE(L"<- rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + return ret; +} + +int rwlockTimedrdlock(ScePthreadRwlock* rwlock, const SceKernelTimespec* t) { + if (t == nullptr) return getErr(ErrCode::_EINVAL); + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + auto ret = (*rwlock)->p.timed_lock_shared(getTimeDuration(t)) ? Ok : getErr(ErrCode::_ETIMEDOUT); + + // LOG_TRACE(L"<- rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + return ret; +} + +int rwlockTimedwrlock(ScePthreadRwlock* rwlock, SceKernelUseconds usec) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rwlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + auto ret = (*rwlock)->p.timed_lock(boost::chrono::duration(boost::chrono::microseconds(usec))) ? Ok : getErr(ErrCode::_ETIMEDOUT); + if (ret == Ok) { + (*rwlock)->isWrite = true; + } + // LOG_TRACE(L"<- rwlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + return ret; +} + +int rwlockTimedwrlock(ScePthreadRwlock* rwlock, const SceKernelTimespec* t) { + if (t == nullptr) return getErr(ErrCode::_EINVAL); + + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + auto const endTime = boost::chrono::high_resolution_clock::now(); + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + auto ret = (*rwlock)->p.timed_lock(getTimeDuration(t)) ? Ok : getErr(ErrCode::_ETIMEDOUT); + if (ret == Ok) { + (*rwlock)->isWrite = true; + } + // LOG_TRACE(L"<- rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + + return ret; +} + +int rwlockTryrdlock(ScePthreadRwlock* rwlock) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + auto ret = (*rwlock)->p.try_lock_shared() ? Ok : getErr(ErrCode::_EBUSY); + // LOG_TRACE(L"<- rdlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + return ret; +} + +int rwlockTrywrlock(ScePthreadRwlock* rwlock) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rwlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + auto ret = (*rwlock)->p.try_lock() ? Ok : getErr(ErrCode::_EBUSY); + if (ret == Ok) { + (*rwlock)->isWrite = true; + } + // LOG_TRACE(L"-> rwlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + return ret; +} + +int rwlockUnlock(ScePthreadRwlock* rwlock) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rdunlock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + + if ((*rwlock)->isWrite) { + (*rwlock)->isWrite = false; + (*rwlock)->p.unlock(); + } else + (*rwlock)->p.unlock_shared(); + return Ok; +} + +int rwlockWrlock(ScePthreadRwlock* rwlock) { + if (int res = checkRwLockInit(rwlock); res != Ok) return res; + + LOG_USE_MODULE(pthread); + // LOG_TRACE(L"-> rwlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + + (*rwlock)->p.lock(); + (*rwlock)->isWrite = true; + // LOG_TRACE(L"<- rwlock lock(%S)| id:%llu result:%d", (*rwlock)->name.c_str(), (*rwlock)->id); + return Ok; +} + +int rwlockattrDestroy(ScePthreadRwlockattr* attr) { + if (attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + delete *attr; + *attr = nullptr; + return Ok; +} + +int rwlockattrSettype(ScePthreadRwlockattr* attr, int type) { + if (attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->type = type; + + return Ok; +} + +int rwlockattrInit(ScePthreadRwlockattr* attr) { + *attr = new PthreadRwlockattrPrivate(); + + return Ok; +} + +int rwlockattrGettype(ScePthreadRwlockattr* attr, int* type) { + if (type == nullptr || attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *type = (*attr)->type; + + return Ok; +} + +int setspecific(ScePthreadKey key, const void* value) { + accessRuntimeLinker().setTLSKey(key, value); + return Ok; +} + +void* getspecific(ScePthreadKey key) { + return accessRuntimeLinker().getTLSKey(key); +} + +int cancel(ScePthread_obj obj) { + if (obj == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto thread = getPthread(obj); + // todo cancel + // int result = ::pthread_cancel(thread->p); + thread->p.interrupt(); + + return Ok; +} + +int setcancelstate(int state, int* oldState) { + auto old = g_cancelState.exchange((SceCancelState)state); + if (oldState != nullptr) *oldState = (int)old; + + return Ok; +} + +int setcanceltype(int type, int* oldType) { + auto old = g_cancelType.exchange((SceCancelType)type); + if (oldType != nullptr) *oldType = (int)old; + + return Ok; +} + +void testCancel(void) { + LOG_USE_MODULE(pthread); + LOG_ERR(L" test cancel"); + // todo + //::pthread_testcancel(); +} + +int getprio(ScePthread_obj obj, int* prio) { + if (obj == nullptr || prio == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto thread = getPthread(obj); + *prio = thread->attr.getShedParam().sched_priority; + return Ok; +} + +int setprio(ScePthread_obj obj, int prio) { + if (obj == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + auto thread = getPthread(obj); + thread->attr.setShedParam({.sched_priority = prio}); + setThreadPrio(thread->p.native_handle(), thread->attr.getShedParam().sched_priority); + return Ok; +} + +void yield(void) { + boost::this_thread::yield(); +} + +int mutexattrGetprioceiling(ScePthreadMutexattr* attr, int* prio) { + *prio = (*attr)->prioCeiling; + return Ok; +} + +int mutexattrSetprioceiling(ScePthreadMutexattr* attr, int prio) { + (*attr)->prioCeiling = prio; + return Ok; +} + +int mutexGetprioceiling(ScePthreadMutex* mutex, int* prio) { + *prio = (*mutex)->prioCeiling; + return Ok; +} + +int mutexSetprioceiling(ScePthreadMutex* mutex, int prio, int* oldPrio) { + if (oldPrio != nullptr) *oldPrio = (*mutex)->prioCeiling; + (*mutex)->prioCeiling = prio; + return Ok; +} + +int mutexattrGetprotocol(ScePthreadMutexattr* attr, int* protocol) { + *protocol = (int)(*attr)->pprotocol; + return Ok; +} + +int mutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol) { + (*attr)->pprotocol = (SceMutexProtocol)protocol; + return Ok; +} + +int mutexattrGetpshared(ScePthreadMutexattr* attr, int* pshared) { + *pshared = (int)(*attr)->pshared; + return Ok; +} + +int mutexattrSetpshared(ScePthreadMutexattr* attr, int pshared) { + (*attr)->pshared = (SceMutexShared)pshared; + return Ok; +} + +int attrGetinheritsched(const ScePthreadAttr* attr, SceInheritShed* inheritSched) { + if (inheritSched == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *inheritSched = (*attr)->getInheritShed(); + return Ok; +} + +int attrGetschedparam(const ScePthreadAttr* attr, SceSchedParam* param) { + if (param == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *param = (*attr)->getShedParam(); + return Ok; +} + +int attrGetschedpolicy(const ScePthreadAttr* attr, SceShedPolicy* policy) { + if (policy == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *policy = (*attr)->getPolicy(); + return Ok; +} + +int attrSetinheritsched(ScePthreadAttr* attr, SceInheritShed inheritSched) { + if (attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setInheritShed(inheritSched); + return Ok; +} + +int attrSetschedparam(ScePthreadAttr* attr, const SceSchedParam* param) { + if (param == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setShedParam(*param); + return Ok; +} + +int attrSetschedpolicy(ScePthreadAttr* attr, SceShedPolicy policy) { + if (attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setPolicy(policy); + return Ok; +} + +int getschedparam(ScePthread_obj obj, int* policy, SceSchedParam* param) { + if (obj == nullptr || param == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + auto thread = getPthread(obj); + if (policy != nullptr) *policy = thread->policy; + *param = thread->attr.getShedParam(); + return Ok; +} + +int setschedparam(ScePthread_obj obj, int policy, const SceSchedParam* param) { + if (obj == nullptr || param == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + auto thread = getPthread(obj); + + thread->policy = policy; + thread->attr.setShedParam(*param); + + return Ok; +} + +int attrGetaffinity(const ScePthreadAttr* attr, SceKernelCpumask* mask) { + if (mask == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *mask = (*attr)->getAffinity(); + + return Ok; +} + +int attrSetaffinity(ScePthreadAttr* attr, const SceKernelCpumask mask) { + if (attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + (*attr)->setAffinity(mask); + return Ok; +} + +int getaffinity(ScePthread_obj obj, SceKernelCpumask* mask) { + if (obj == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + auto thread = getPthread(obj); + *mask = thread->attr.getAffinity(); + return Ok; +} + +int setaffinity(ScePthread_obj obj, const SceKernelCpumask mask) { + if (obj == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + auto thread = getPthread(obj); + int result = Ok; + thread->attr.setAffinity(mask); + + return result; +} + +ScePthread_obj& getSelf() { + thread_local ScePthread_obj g_pthread_obj = nullptr; + return g_pthread_obj; +} + +int getthreadid(void) { + return getPthread(getSelf())->unique_id; +} + +int once(ScePthreadOnce once_control, pthread_once_init init_routine) { + if (once_control == nullptr || init_routine == nullptr) return getErr(ErrCode::_EINVAL); + + auto res = mutexLock(&once_control->mutex); + + if (once_control->isInit == 0) { + init_routine(); + once_control->isInit = 1; + } + + res = mutexUnlock(&once_control->mutex); + + return Ok; +} + +int rename(ScePthread_obj obj, const char* name) { + if (obj == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + auto thread = getPthread(obj); + thread->name = std::format("_{}_{}", thread->unique_id, name); + util::setThreadName(thread->name, (void*)thread->p.native_handle()); + return Ok; +} + +int getName(ScePthread_obj obj, char* name) { + if (obj == nullptr) { + return getErr(ErrCode::_ESRCH); + } + + auto thread = getPthread(obj); + strcpy_s(name, 32, thread->name.c_str()); + return Ok; +} + +void setThreadDtors(thread_dtors_func_t dtors) { + *getThreadDtors() = dtors; +} + +auto attrInit_intern() { + auto attr = std::make_unique().release(); + + return attr; +} + +int attrInit(ScePthreadAttr* attr) { + LOG_USE_MODULE(pthread); + *attr = attrInit_intern(); + return *attr != nullptr ? Ok : getErr(ErrCode::_ENOMEM); +} + +int attrGet(ScePthread_obj obj, ScePthreadAttr* attr) { + if (obj == nullptr || attr == nullptr || *attr == nullptr) { + return getErr(ErrCode::_EINVAL); + } + + *attr = attrInit_intern(); + auto thread = getPthread(obj); + **attr = thread->attr; + return Ok; +} + +void exit(void* value) { + LOG_USE_MODULE(pthread); + + auto thread = getPthread(getSelf()); + + // todo: unwinding + thread->arg = value; + unwinding_longjmp(&thread->_unwinding); +} + +void raise(ScePthread_obj obj, void* callback, int signo) { + auto thread = getPthread(obj); + QueueUserAPC2((PAPCFUNC)callback, thread->p.native_handle(), (ULONG_PTR)signo, QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC); +} + +void* threadWrapper(void* arg); + +int create(ScePthread_obj* obj, const ScePthreadAttr* attr, pthread_entry_func_t entry, void* arg, const char* name) { + // Init pthread once + { + static boost::once_flag once; + boost::call_once(once, init_pThread); + } + // - + + LOG_USE_MODULE(pthread); + + if (obj == nullptr) { + return getErr(ErrCode::_EINVAL); + } + std::unique_lock lock(getData()->mutex); + + // Create thread + auto const tlsStaticSize = accessRuntimeLinker().getTLSStaticBlockSize(); + *obj = new uint8_t[sizeof(tlsStaticSize) + tlsStaticSize + sizeof(PthreadPrivate)] {}; + ((uint64_t*)*obj)[0] = sizeof(tlsStaticSize) + tlsStaticSize; // pthreadOffset + + auto pthread = getPthread(*obj); + pthread = new (pthread) PthreadPrivate(); + + initTLS(*obj); + // - + + auto thread = getPthread(*obj); + + if (attr != nullptr) thread->attr = **attr; + + thread->unique_id = threadCounter(); + + if (name != nullptr) + thread->name = std::format("_{}_{}", thread->unique_id, sanThreadName(name)); + else + thread->name = std::format("_{}", thread->unique_id); + + thread->entry = entry; + thread->arg = arg; + thread->started = false; + thread->detached = thread->attr.getDetachState() == SceDetachState::DETACHED; + + boost::thread::attributes boostAttr; + boostAttr.set_stack_size(thread->attr.getStackSize()); + //!! start_thread_noexcept in boost shouldn't contain STACK_SIZE_PARAM_IS_A_RESERVATION! + + thread->p = boost::thread(boostAttr, boost::bind(threadWrapper, *obj)); + + setThreadPrio(thread->p.native_handle(), thread->attr.getShedParam().sched_priority); + thread->started.wait(true); // race cond with wrapped thread start + if (thread->attr.getDetachState() == SceDetachState::DETACHED) { + // thread->p.detach(); // Signaling (raiseSignal) doesnt work when detached + } + LOG_INFO(L"--> threadId:%d name:%S addr:0x%08llx stackSize:0x%08llx detached:%d parent:%d", thread->unique_id, thread->name.c_str(), &thread->p, + thread->attr.getStackSize(), thread->detached, getSelf() != nullptr ? getThreadId() : 0); + return Ok; +} + +int attrSetscope(ScePthreadAttr* attr, int flag) { + (*attr)->setScope(flag); + return Ok; +} + +void cxa_finalize(void* /*p*/) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"pthread_cxa_finalize"); +} + +void cleanup_push(thread_clean_func_t func, void* arg) { + auto thread = getPthread(getSelf()); + thread->cleanupFuncs.push_back(std::make_pair(func, arg)); +} + +void cleanup_pop(int execute) { + auto thread = getPthread(getSelf()); + if (execute > 0) { + auto const& [func, arg] = thread->cleanupFuncs.back(); + func(arg); + } + thread->cleanupFuncs.pop_back(); +} + +int attrGetscope(ScePthreadAttr* attr, int* flag) { + *flag = (*attr)->getScope(); + return Ok; +} + +uint8_t* getTLSStaticBlock(ScePthread_obj obj) { + return (uint8_t*)((uint64_t*)obj)[1]; // 0: size +} + +uint64_t* getDTV(ScePthread_obj obj) { + auto thread = getPthread(obj); + return thread->dtv; +} + +int getThreadId() { + return getPthread(getSelf())->unique_id; +} + +int getThreadId(ScePthread_obj obj) { + return getPthread(obj)->unique_id; +} + +void cleanup_thread() { + auto thread = getPthread(getSelf()); + LOG_USE_MODULE(pthread); + LOG_DEBUG(L"cleanup thread:%d", thread->unique_id); + + while (!thread->cleanupFuncs.empty()) { + auto const& [func, arg] = thread->cleanupFuncs.back(); + func(arg); + thread->cleanupFuncs.pop_back(); + } + accessRuntimeLinker().destroyTLSKeys(getSelf()); + + auto thread_dtors = *getThreadDtors(); + + if (thread_dtors != nullptr) { + thread_dtors(); + } + + accessMemoryManager()->unregisterStack((uint64_t)thread->attr.getStackAddr()); + + // Delete here if detached, else in join() + if (thread->detached) { + LOG_DEBUG(L"Delete thread:%d", thread->unique_id); + thread->p.detach(); + thread->~PthreadPrivate(); + + delete[] getSelf(); + } +} + +ScePthread setup_thread(void* arg) { + auto& gSelf = getSelf(); + gSelf = static_cast(arg); + auto thread = getPthread(gSelf); + auto& attr = thread->attr; + + LOG_USE_MODULE(pthread); + // Set Stack Data -> for page_fault allocs + { + NT_TIB* tib = getTIB(); + + attr.setStackAddr(tib->StackLimit); // lowest addressable byte + auto const stackSize = (uint64_t)tib->StackBase - (uint64_t)tib->StackLimit; + + accessMemoryManager()->registerStack((uint64_t)tib->StackLimit, stackSize); + + LOG_DEBUG(L"thread[%d] stack addr:0x%08llx size:0x%08llx", thread->unique_id, tib->StackBase, stackSize); + if (attr.getStackSize() < stackSize) { + LOG_USE_MODULE(pthread); + LOG_ERR(L"wrong stack size"); + } + attr.setStackSize(stackSize); + } + // - + + boost::this_thread::at_thread_exit(cleanup_thread); + thread->started = true; + thread->started.notify_one(); + util::setThreadName(thread->name); + + return thread; +} + +void* threadWrapper(void* arg) { + void* ret = 0; + auto thread = setup_thread(arg); + auto addr = &setup_thread; + + int oldType = 0; + LOG_USE_MODULE(pthread); + + if (unwinding_setjmp(&thread->_unwinding)) { + ret = thread->entry(thread->arg); + } else + ret = thread->arg; + + LOG_DEBUG(L"thread done:%d", thread->unique_id); + return ret; +} +} // namespace pthread diff --git a/core/kernel/pthread.h b/core/kernel/pthread.h new file mode 100644 index 00000000..25d37867 --- /dev/null +++ b/core/kernel/pthread.h @@ -0,0 +1,195 @@ +#pragma once +#include "pthread_types.h" +#include "utility/utility.h" + +#include +#include + +struct PthreadAttrPrivate; +struct PthreadBarrierPrivate; +struct PthreadBarrierattrPrivate; +struct PthreadCondattrPrivate; +struct PthreadCondPrivate; +struct PthreadMutexPrivate; +struct PthreadMutexattrPrivate; +struct PthreadRwlockPrivate; +struct PthreadRwlockattrPrivate; +struct PthreadPrivate; +struct PthreadOnce; + +using ScePthread_obj = uint8_t*; +using ScePthreadAttr = PthreadAttrPrivate*; +using ScePthreadBarrier = PthreadBarrierPrivate*; +using ScePthreadBarrierattr = PthreadBarrierattrPrivate*; +using ScePthreadCondattr = PthreadCondattrPrivate*; +using ScePthreadCond = PthreadCondPrivate*; +using ScePthreadMutex = PthreadMutexPrivate*; +using ScePthreadMutexattr = PthreadMutexattrPrivate*; +using ScePthreadRwlock = PthreadRwlockPrivate*; +using ScePthreadRwlockattr = PthreadRwlockattrPrivate*; +using ScePthread = PthreadPrivate*; +using ScePthreadOnce = PthreadOnce*; +using ScePthreadKey = int; + +#if defined(__APICALL_PTHREAD_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +struct DTVKey; + +namespace pthread { + +__APICALL uint8_t* getTLSStaticBlock(ScePthread_obj obj); +__APICALL uint64_t* getDTV(ScePthread_obj obj); + +__APICALL int getThreadId(); +__APICALL int getThreadId(ScePthread_obj obj); + +__APICALL ScePthread_obj& getSelf(); + +__APICALL int attrDestroy(ScePthreadAttr* attr); +__APICALL int attrGetstack(const ScePthreadAttr* attr, void** stackAddr, size_t* stackSize); +__APICALL int attrGetstacksize(const ScePthreadAttr* attr, size_t* stackSize); +__APICALL int attrGetguardsize(const ScePthreadAttr* attr, size_t* guardSize); +__APICALL int attrGetstackaddr(const ScePthreadAttr* attr, void** stackAddr); +__APICALL int attrGetdetachstate(const ScePthreadAttr* attr, SceDetachState* state); +__APICALL int attrSetstacksize(ScePthreadAttr* attr, size_t stackSize); +__APICALL int attrSetguardsize(ScePthreadAttr* attr, size_t guardSize); +__APICALL int attrSetstack(ScePthreadAttr* attr, void* addr, size_t size); +__APICALL int attrSetstackaddr(ScePthreadAttr* attr, void* addr); +__APICALL int attrSetdetachstate(ScePthreadAttr* attr, SceDetachState state); + +__APICALL int barrierDestroy(ScePthreadBarrier* barrier); +__APICALL int barrierInit(ScePthreadBarrier* barrier, const ScePthreadBarrierattr* attr, unsigned count, const char* name); +__APICALL int barrierWait(ScePthreadBarrier* barrier); + +__APICALL int barrierattrDestroy(ScePthreadBarrierattr* barrier); +__APICALL int barrierattrInit(ScePthreadBarrierattr* barrier); +__APICALL int barrierattrGetpshared(ScePthreadBarrierattr* barrier, int* pshared); +__APICALL int barrierattrSetpshared(ScePthreadBarrierattr* barrier, int pshared); + +__APICALL int condattrDestroy(ScePthreadCondattr* attr); +__APICALL int condattrInit(ScePthreadCondattr* attr); + +__APICALL int condInit(ScePthreadCond* cond, const ScePthreadCondattr* attr, const char* name); +__APICALL int condBroadcast(ScePthreadCond* cond); +__APICALL int condDestroy(ScePthreadCond* cond); +__APICALL int condSignal(ScePthreadCond* cond); +__APICALL int condSignalto(ScePthreadCond* cond, ScePthread_obj obj); +__APICALL int condTimedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, SceKernelUseconds usec); +__APICALL int condTimedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, const SceKernelTimespec* t); +__APICALL int condWait(ScePthreadCond* cond, ScePthreadMutex* mutex); + +__APICALL int detach(ScePthread_obj obj); +__APICALL int equal(ScePthread_obj thread1, ScePthread_obj thread2); + +__APICALL int getcpuclockid(ScePthread_obj thread, int* clock); + +__APICALL int join(ScePthread_obj obj, void** value); + +__APICALL int keyDelete(ScePthreadKey key); +__APICALL int keyCreate(ScePthreadKey* key, pthread_key_destructor_func_t destructor); + +__APICALL int mutexattrInit(ScePthreadMutexattr* attr); +__APICALL int mutexattrDestroy(ScePthreadMutexattr* attr); +__APICALL int mutexattrGettype(ScePthreadMutexattr* attr, int* type); +__APICALL int mutexattrSettype(ScePthreadMutexattr* attr, int type); + +__APICALL int mutexDestroy(ScePthreadMutex* mutex); +__APICALL int mutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, const char* name); +__APICALL int mutexLock(ScePthreadMutex* mutex); +__APICALL int mutexTrylock(ScePthreadMutex* mutex); +__APICALL int mutexTimedlock(ScePthreadMutex* mutex, SceKernelUseconds usec); +__APICALL int mutexTimedlock(ScePthreadMutex* mutex, const SceKernelTimespec* t); +__APICALL int mutexUnlock(ScePthreadMutex* mutex); + +__APICALL int rwlockDestroy(ScePthreadRwlock* rwlock); +__APICALL int rwlockInit(ScePthreadRwlock* rwlock, const ScePthreadRwlockattr* attr, const char* name); + +__APICALL int rwlockRdlock(ScePthreadRwlock* rwlock); +__APICALL int rwlockTimedrdlock(ScePthreadRwlock* rwlock, SceKernelUseconds usec); +__APICALL int rwlockTimedrdlock(ScePthreadRwlock* rwlock, const SceKernelTimespec* t); +__APICALL int rwlockTimedwrlock(ScePthreadRwlock* rwlock, SceKernelUseconds usec); +__APICALL int rwlockTimedwrlock(ScePthreadRwlock* rwlock, const SceKernelTimespec* t); +__APICALL int rwlockTryrdlock(ScePthreadRwlock* rwlock); +__APICALL int rwlockTrywrlock(ScePthreadRwlock* rwlock); +__APICALL int rwlockUnlock(ScePthreadRwlock* rwlock); +__APICALL int rwlockWrlock(ScePthreadRwlock* rwlock); + +__APICALL int rwlockattrDestroy(ScePthreadRwlockattr* attr); +__APICALL int rwlockattrSettype(ScePthreadRwlockattr* attr, int type); +__APICALL int rwlockattrInit(ScePthreadRwlockattr* attr); +__APICALL int rwlockattrGettype(ScePthreadRwlockattr* attr, int* type); + +__APICALL int setspecific(ScePthreadKey key, const void* value); +__APICALL void* getspecific(ScePthreadKey key); + +__APICALL int cancel(ScePthread_obj obj); + +__APICALL int setcancelstate(int state, int* oldState); +__APICALL int setcanceltype(int type, int* oldType); + +__APICALL void testCancel(void); + +__APICALL int getprio(ScePthread_obj obj, int* prio); +__APICALL int setprio(ScePthread_obj obj, int prio); + +__APICALL void yield(void); + +__APICALL int mutexattrGetprioceiling(ScePthreadMutexattr* attr, int* prio); +__APICALL int mutexattrSetprioceiling(ScePthreadMutexattr* attr, int prio); +__APICALL int mutexGetprioceiling(ScePthreadMutex* mutex, int* prio); +__APICALL int mutexSetprioceiling(ScePthreadMutex* mutex, int prio, int* oldPrio); +__APICALL int mutexattrGetprotocol(ScePthreadMutexattr* attr, int* protocol); +__APICALL int mutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol); +__APICALL int mutexattrGetpshared(ScePthreadMutexattr* attr, int* pshared); +__APICALL int mutexattrSetpshared(ScePthreadMutexattr* attr, int pshared); + +__APICALL int attrGetinheritsched(const ScePthreadAttr* attr, SceInheritShed* inheritSched); +__APICALL int attrGetschedparam(const ScePthreadAttr* attr, SceSchedParam* param); +__APICALL int attrGetschedpolicy(const ScePthreadAttr* attr, SceShedPolicy* policy); +__APICALL int attrSetinheritsched(ScePthreadAttr* attr, SceInheritShed inheritSched); +__APICALL int attrSetschedparam(ScePthreadAttr* attr, const SceSchedParam* param); +__APICALL int attrSetschedpolicy(ScePthreadAttr* attr, SceShedPolicy policy); + +__APICALL int getschedparam(ScePthread_obj obj, int* policy, SceSchedParam* param); +__APICALL int setschedparam(ScePthread_obj obj, int policy, const SceSchedParam* param); + +__APICALL int attrGet(ScePthread_obj obj, ScePthreadAttr* attr); + +__APICALL int attrGetaffinity(const ScePthreadAttr* attr, SceKernelCpumask* mask); +__APICALL int attrSetaffinity(ScePthreadAttr* attr, const SceKernelCpumask mask); + +__APICALL int getaffinity(ScePthread_obj obj, SceKernelCpumask* mask); +__APICALL int setaffinity(ScePthread_obj obj, const SceKernelCpumask mask); + +__APICALL int getthreadid(void); +__APICALL int once(ScePthreadOnce once_control, pthread_once_init init_routine); + +__APICALL int rename(ScePthread_obj obj, const char* name); +__APICALL int getName(ScePthread_obj obj, char* name); + +__APICALL void setThreadDtors(thread_dtors_func_t dtors); + +__APICALL int attrInit(ScePthreadAttr* attr); + +__APICALL void exit(void* value); +__APICALL void raise(ScePthread_obj obj, void* callback, int signo); + +__APICALL int create(ScePthread_obj* obj, const ScePthreadAttr* attr, pthread_entry_func_t entry, void* arg, const char* name); + +__APICALL int attrSetscope(ScePthreadAttr* attr, int flag); +__APICALL int attrGetscope(ScePthreadAttr* attr, int* flag); + +__APICALL void cxa_finalize(void* /*p*/); + +__APICALL void cleanup_push(thread_clean_func_t func, void* arg); +__APICALL void cleanup_pop(int execute); + +} // namespace pthread + +#undef __APICALL diff --git a/core/kernel/pthread_intern.h b/core/kernel/pthread_intern.h new file mode 100644 index 00000000..1d2d24e6 --- /dev/null +++ b/core/kernel/pthread_intern.h @@ -0,0 +1,169 @@ +#pragma once +#include "core/unwinding/unwind.h" +#include "modules_include/common.h" +#include "pthread_types.h" +#include "utility/utility.h" + +#include +#include + +constexpr size_t DEFAULT_STACKSIZE = 16 * 1024 * 1024; +constexpr uint64_t XSAVE_CHK_GUARD = 0xDeadBeef5533CCAAu; + +constexpr int KEYS_MAX = 256; +constexpr int DESTRUCTOR_ITERATIONS = 4; + +constexpr size_t DTV_MAX_KEYS = 256; + +struct PthreadAttrPrivate { + private: + size_t stackSize = DEFAULT_STACKSIZE; + void* stackAddr = 0; /// if != 0 use the custum stack + + SceSchedParam shedParam = {.sched_priority = 700}; + SceKernelCpumask affinity = 0x7f; + size_t guardSize = 0x1000; + SceShedPolicy policy = SceShedPolicy::FIFO; + int scope = 0; + SceDetachState detachState = SceDetachState::JOINABLE; + + SceInheritShed inheritShed = SceInheritShed::INHERIT; + + public: + PthreadAttrPrivate() = default; + + auto getStackAddr() const noexcept { return stackAddr; } + + void setStackAddr(decltype(PthreadAttrPrivate::stackAddr) param) noexcept { stackAddr = param; } + + auto getStackSize() const noexcept { return stackSize; } + + void setStackSize(decltype(PthreadAttrPrivate::stackSize) param) noexcept { stackSize = param; } + + auto getGuardSize() const noexcept { return guardSize; } + + void setGuardSize(decltype(PthreadAttrPrivate::guardSize) param) noexcept { guardSize = param; } + + auto getAffinity() const noexcept { return affinity; } + + void setAffinity(decltype(PthreadAttrPrivate::affinity) param) noexcept { affinity = param; } + + auto getPolicy() const noexcept { return policy; } + + void setPolicy(decltype(PthreadAttrPrivate::policy) param) noexcept { policy = param; } + + auto getScope() const noexcept { return scope; } + + void setScope(decltype(PthreadAttrPrivate::scope) param) noexcept { scope = param; } + + auto getShedParam() const noexcept { return shedParam; } + + void setShedParam(decltype(PthreadAttrPrivate::shedParam) param) noexcept { shedParam = param; } + + auto getDetachState() const noexcept { return detachState; } + + void setDetachState(decltype(PthreadAttrPrivate::detachState) param) noexcept { detachState = param; } + + auto getInheritShed() const noexcept { return inheritShed; } + + void setInheritShed(decltype(PthreadAttrPrivate::inheritShed) param) noexcept { inheritShed = param; } +}; + +struct PthreadMutexPrivate { + uint64_t initialized = 1; + uint8_t reserved[256]; // type is bigger on ps than here + boost::recursive_mutex p; + std::string name; + size_t id; + int prioCeiling; + SceMutexType type = SceMutexType::DEFAULT; + + ~PthreadMutexPrivate() {} +}; + +struct PthreadMutexattrPrivate { + uint64_t initialized = 1; + uint8_t reserved[60]; // type is bigger on ps than here + + SceMutexShared pshared = SceMutexShared::PRIVATE; + SceMutexType type = SceMutexType::DEFAULT; + SceMutexProtocol pprotocol = SceMutexProtocol::PRIO_NONE; + int prioCeiling; +}; + +struct DTVKey { + bool used = false; + + pthread_key_destructor_func_t destructor = nullptr; +}; + +constexpr size_t DTV_SIZE = DTV_MAX_KEYS; + +struct PthreadPrivate { + + uint64_t dtv[DTV_SIZE]; + std::string name; + + boost::thread p; + + PthreadAttrPrivate attr = {}; + pthread_entry_func_t entry = nullptr; + void* arg = nullptr; + int unique_id = 0; + std::atomic_bool started = false; + int policy = 0; + + std::vector> cleanupFuncs; + + bool detached = false; // fake detach + + unwinding_jmp_buf _unwinding; + ~PthreadPrivate() = default; +}; + +struct PthreadRwlockPrivate { + uint64_t initialized = 1; + uint8_t reserved[256]; // type is bigger on ps than here + + boost::shared_mutex p; + std::string name; + size_t id; + + bool isWrite = false; + + ~PthreadRwlockPrivate() {} +}; + +struct PthreadBarrierPrivate {}; + +struct PthreadBarrierattrPrivate { + int pshared; +}; + +struct PthreadCondattrPrivate { + uint64_t initialized = 1; + uint8_t reserved[64]; // type is bigger on ps than here +}; + +struct PthreadRwlockattrPrivate { + uint64_t initialized = 1; + uint8_t reserved[64]; // type is bigger on ps than here + int type; +}; + +struct PthreadCondPrivate { + uint64_t initialized = 1; + uint8_t reserved[64]; // type is bigger on ps than here + + boost::condition_variable p; + std::string name; + + ~PthreadCondPrivate() {} +}; + +struct PthreadOnce { + int32_t isInit; // 0: no, 1: yes + int32_t _align; + + PthreadMutexPrivate* mutex; +}; diff --git a/core/kernel/pthread_types.h b/core/kernel/pthread_types.h new file mode 100644 index 00000000..9e1ee099 --- /dev/null +++ b/core/kernel/pthread_types.h @@ -0,0 +1,51 @@ +#pragma once +#include "utility/utility.h" + +// clang-format off + +using pthread_entry_func_t = SYSV_ABI void* (*)(void*); +using pthread_key_destructor_func_t = SYSV_ABI void (*)(void*); +using thread_dtors_func_t = SYSV_ABI void (*)(); +using thread_clean_func_t = SYSV_ABI void (*)(void*); +using pthread_once_init = SYSV_ABI void (*)(); + +// clang-format on + +struct SceSchedParam { + int sched_priority; +}; + +enum class SceMutexProtocol { PRIO_NONE, PRIO_INHERIT, PRIO_PROTECT }; +enum class SceMutexType { DEFAULT, ERRORCHECK, RECURSIVE, NORMAL, ADAPTIVE_NP }; +enum class SceMutexShared { PRIVATE, SHARED }; + +enum class SceDetachState { + JOINABLE, + DETACHED, +}; + +enum class SceShedPolicy { + OTHER, + FIFO, + RR, +}; +enum class SceInheritShed { + EXPLICIT, + INHERIT = 4, +}; + +enum class SceCancelState { + ENABLE, + DISABLE, +}; +enum class SceCancelType { + DEFERRED, + ASYNC = 4, +}; + +struct SceCleanInfo { + SceCleanInfo* prev; + thread_clean_func_t func; + void* arg; + int onHeap; +}; diff --git a/core/kernel/semaphore.h b/core/kernel/semaphore.h new file mode 100644 index 00000000..4ad29e20 --- /dev/null +++ b/core/kernel/semaphore.h @@ -0,0 +1,40 @@ +#pragma once +#include "utility/utility.h" + +#include + +class ISemaphore { + CLASS_NO_COPY(ISemaphore); + CLASS_NO_MOVE(ISemaphore); + + protected: + ISemaphore() = default; + + public: + virtual ~ISemaphore() = default; + + virtual int cancel(int setCount, int* numCanceled) = 0; + virtual int signal(int signalCount) = 0; + virtual int wait(int needcount, uint32_t* pMicros) = 0; + virtual int try_wait(int needcount, uint32_t* pMicros) = 0; + virtual int poll(int needCount) = 0; + + virtual std::string_view const getName() const = 0; + + virtual size_t getId() const = 0; + + virtual size_t getSignalCounter() const = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +__APICALL std::unique_ptr createSemaphore_fifo(const char* name, int initCount, int maxCount); +__APICALL std::unique_ptr createSemaphore_prio(const char* name, int initCount, int maxCount); + +#undef __APICALL \ No newline at end of file diff --git a/core/kernel/semaphore_fifo.cpp b/core/kernel/semaphore_fifo.cpp new file mode 100644 index 00000000..5ca1149b --- /dev/null +++ b/core/kernel/semaphore_fifo.cpp @@ -0,0 +1,311 @@ +#define __APICALL_EXTERN +#include "semaphore.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "modules_include/common.h" +#include "pthread.h" + +#include +#include +#include +#include +#include + +LOG_DEFINE_MODULE(Semaphore); + +namespace { +enum class SemState { idle, signaled, waiting, canceled }; + +struct SemData { + size_t index = 0; + + uint32_t needs = 0; + + SemState state = SemState::idle; + + boost::condition_variable m_condVar; + + SemData* parent = nullptr; + SemData* child = nullptr; +}; +} // namespace + +class Semaphore: public ISemaphore { + boost::mutex m_mutexInt; + + boost::condition_variable m_condState; + + size_t const m_id = [] { + static size_t count = 0; + return count++; + }(); + + std::string const m_name; + int const m_maxCount; + + uint32_t m_signalValue = 0; + + bool m_isStop = false; + + std::map> m_semData; // todo use pthread specific + + SemData* m_curBack = nullptr; + SemData* m_curStart = nullptr; + + size_t m_numWaiters = 0; + + size_t m_countWaits = 0; + + public: + Semaphore(const std::string& name, int initCount, int maxCount): m_name(name), m_maxCount(maxCount) { m_signalValue = initCount; }; + + virtual ~Semaphore() { + m_isStop = true; + + boost::unique_lock lock(m_mutexInt); + + while (m_curStart != nullptr) { + m_curStart->m_condVar.notify_one(); + m_curStart = m_curStart->child; + } + + m_condState.wait(lock, [this] { return m_numWaiters == 0; }); + } + + // ### Interface + std::string_view const getName() const final { return m_name; } + + size_t getSignalCounter() const final { return m_signalValue; } + + size_t getId() const final { return m_id; } + + int cancel(int setCount, int* numCanceled) final; + int signal(int signalCount) final; + int wait(int needcount, uint32_t* pMicros) final; + int try_wait(int needcount, uint32_t* pMicros) final; + int poll(int needCount) final; + + private: + int wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock& lock); + + int poll_internal(int needCount, boost::unique_lock& lock); +}; + +std::unique_ptr createSemaphore_fifo(const char* name, int initCount, int maxCount) { + return std::make_unique(name == nullptr ? "" : name, initCount, maxCount); +} + +int Semaphore::cancel(int setCount, int* numCanceled) { + LOG_USE_MODULE(Semaphore); + + if (setCount <= 0 || setCount >= m_maxCount) return getErr(ErrCode::_EINVAL); + + boost::unique_lock lock(m_mutexInt); + + *numCanceled = 0; + auto start = m_curStart; + while (start != nullptr) { + if (start->needs > setCount) break; + + ++*numCanceled; + setCount -= start->needs; + start->state = SemState::canceled; + + start = start->child; // next + } + + m_signalValue = 0; + + // Notify back + if (*numCanceled > 0) { + lock.unlock(); + start->m_condVar.notify_one(); + } + // - + return Ok; +} + +int Semaphore::signal(int signalCount) { + LOG_USE_MODULE(Semaphore); + + boost::unique_lock lock(m_mutexInt); + + m_signalValue += signalCount; + + // Notify back + if (m_curStart != nullptr && m_curStart->needs <= m_signalValue) { + m_curStart->state = SemState::signaled; + LOG_TRACE(L"KernelSema(%llu) name:%S notify| count:%d index:%llu", m_id, m_name.c_str(), m_signalValue, m_curStart->index); + + auto& cond = m_curStart->m_condVar; + lock.unlock(); + cond.notify_one(); // race condition if m_curStart is used + } else { + LOG_TRACE(L"KernelSema(%llu) name:%S signal| count:%d", m_id, m_name.c_str(), m_signalValue); + } + // - + return Ok; +} + +int Semaphore::wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock& lock) { + LOG_USE_MODULE(Semaphore); + + if (poll_internal(needCount, lock) == Ok) { + return Ok; + } + + auto itThread = m_semData.find(pthread::getThreadId()); + if (itThread == m_semData.end()) { + itThread = m_semData.emplace(std::make_pair(pthread::getThreadId(), std::make_shared())).first; + } + + ++m_numWaiters; + + auto ownData = itThread->second; + ownData->index = ++m_countWaits; + ownData->needs = needCount; + + // enque in list + if (m_curBack != nullptr) { + m_curBack->child = ownData.get(); + } + if (m_curStart == nullptr) { + m_curStart = ownData.get(); + } + ownData->parent = m_curBack; + m_curBack = ownData.get(); + // - list + + int ret = Ok; + ownData->state = SemState::waiting; + + LOG_TRACE(L"-> KernelSema(%llu) name:%S wait| count:%d needs:%d index:%llu state:%d", m_id, m_name.c_str(), m_signalValue, needCount, ownData->index, + ownData->state); + + if (pMicros == nullptr) { + ownData->m_condVar.wait(lock, [this, ownData] { return m_isStop || ownData->state != SemState::waiting; }); + } else { + std::chrono::time_point startTime = std::chrono::system_clock::now(); + + if (!ownData->m_condVar.wait_for(lock, boost::chrono::microseconds(*pMicros), + [this, ownData] { return m_isStop || ownData->state != SemState::waiting; })) { + // timeout + ret = getErr(ErrCode::_ETIMEDOUT); + + // remove from list + if (ownData->parent != nullptr) { + ownData->parent->child = ownData->child; + } + if (ownData->child != nullptr) { + ownData->child->parent = ownData->parent; + } + + LOG_TRACE(L"<- KernelSema(%llu) name:%S timeout| count:%d needs:%d index:%llu", m_id, m_name.c_str(), m_signalValue, needCount, ownData->index); + + // Special: first in list -> notify next + if (ownData->parent == nullptr) { + m_curStart = ownData->child; + if (m_curStart != nullptr && (m_isStop || m_curStart->needs <= m_signalValue)) { + m_curStart->state = SemState::signaled; + LOG_TRACE(L"KernelSema(%llu) name:%S timeout notify| count:%d index:%llu", m_id, m_name.c_str(), m_signalValue, m_curStart->index); + + auto& cond = m_curStart->m_condVar; + lock.unlock(); + cond.notify_one(); // race condition if m_curStart is used + } + } + // - special + + // Reset ownData + ownData->child = nullptr; + ownData->parent = nullptr; + ownData->state = SemState::idle; + + *pMicros = 0; + + if (--m_numWaiters == 0 && m_isStop) { + lock.unlock(); + m_condState.notify_one(); + } + return ret; + } + + auto elapsed = std::chrono::duration_cast(std::chrono::system_clock::now() - startTime).count(); + + if (pMicros != nullptr) { + *pMicros = (elapsed >= *pMicros ? 0 : *pMicros - elapsed); + } + } + + if (m_isStop || ownData->state == SemState::canceled) { + ret = getErr(ErrCode::_ECANCELED); + } + + if (ret == Ok) { + m_signalValue -= ownData->needs; + } + + // Set list start/end + if (ownData->child == nullptr) { + // Reached end -> reset back + m_curBack = nullptr; + m_countWaits = 0; + } + m_curStart = ownData->child; + // - + LOG_TRACE(L"<- KernelSema(%llu) name:%S wait| count:%d needs:%d index:%llu state:%d", m_id, m_name.c_str(), m_signalValue, needCount, ownData->index, + ownData->state); + + // Reset ownData + ownData->child = nullptr; + ownData->parent = nullptr; + ownData->state = SemState::idle; + // - + + // notify next + if (m_curStart != nullptr && (m_isStop || m_curStart->needs <= m_signalValue)) { + m_curStart->state = SemState::signaled; + LOG_TRACE(L"KernelSema(%llu) name:%S notify| count:%d index:%llu", m_id, m_name.c_str(), m_signalValue, m_curStart->index); + + auto& cond = m_curStart->m_condVar; + lock.unlock(); + cond.notify_one(); // race condition if m_curStart is used + } + // + + if (--m_numWaiters == 0 && m_isStop) { + lock.unlock(); + m_condState.notify_one(); + } + return ret; +} + +int Semaphore::wait(int needcount, uint32_t* pMicros) { + boost::unique_lock lock(m_mutexInt); + return wait_internal(needcount, pMicros, lock); +} + +int Semaphore::try_wait(int needcount, uint32_t* pMicros) { + boost::unique_lock lock(m_mutexInt); + + if (m_curBack != nullptr) { + return getErr(ErrCode::_EBUSY); + } + + return wait_internal(needcount, pMicros, lock); +} + +int Semaphore::poll_internal(int needCount, boost::unique_lock& lock) { + if (m_curBack == 0 && (needCount <= m_signalValue)) { + m_signalValue -= needCount; + return Ok; + } + + return getErr(ErrCode::_EAGAIN); // Waiters in queue, todo need enqueue? +} + +int Semaphore::poll(int needCount) { + boost::unique_lock lock(m_mutexInt); + return poll_internal(needCount, lock); +} diff --git a/core/memory/CMakeLists.txt b/core/memory/CMakeLists.txt new file mode 100644 index 00000000..fe803b8f --- /dev/null +++ b/core/memory/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(memory OBJECT + memory.cpp +) + +add_dependencies(memory third_party psOff_utility) + +file(COPY memory.h DESTINATION ${DST_INCLUDE_DIR}/memory) \ No newline at end of file diff --git a/core/memory/memory.cpp b/core/memory/memory.cpp new file mode 100644 index 00000000..f419f2a6 --- /dev/null +++ b/core/memory/memory.cpp @@ -0,0 +1,339 @@ +#define __APICALL_EXTERN +#include "memory.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "utility/utility.h" + +#include +#include + +LOG_DEFINE_MODULE(memory); + +namespace { +bool checkIsGPU(int protection) { + return (protection & 0xf0) > 0; +} + +DWORD convProtection(int prot) { + switch (prot & 0xf) { + case 0: return PAGE_NOACCESS; + case 1: return PAGE_READONLY; + case 2: + case 3: return PAGE_READWRITE; + case 4: return PAGE_EXECUTE; + case 5: return PAGE_EXECUTE_READ; + case 6: + case 7: return PAGE_EXECUTE_READWRITE; + } + + if (checkIsGPU(prot)) { + return PAGE_READWRITE; // special case: cpu read/writes gpumemory on host memory + } + + return PAGE_NOACCESS; +} + +int convProtection(DWORD prot) { + switch (prot) { + case PAGE_NOACCESS: return 0; + case PAGE_READONLY: return SceProtRead; + case PAGE_READWRITE: return SceProtRead | SceProtWrite; + case PAGE_EXECUTE: return SceProtExecute; + case PAGE_EXECUTE_READ: return SceProtExecute | SceProtRead; + case PAGE_EXECUTE_READWRITE: return SceProtRead | SceProtWrite | SceProtExecute; + } + + return 0; +} + +using VirtualAlloc2_func_t = /*WINBASEAPI*/ PVOID WINAPI (*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG); + +VirtualAlloc2_func_t getVirtualAlloc2() { + static auto funcVirtualAlloc2 = [] { + LOG_USE_MODULE(memory); + HMODULE h = GetModuleHandle("KernelBase"); + if (h != nullptr) { + auto const addr = reinterpret_cast(GetProcAddress(h, "VirtualAlloc2")); + if (addr == nullptr) { + LOG_CRIT(L"virtual_alloc2 == nullptr"); + } + return addr; + } + LOG_CRIT(L"KernelBase not found"); + return (VirtualAlloc2_func_t) nullptr; + }(); + + return funcVirtualAlloc2; +} +} // namespace + +namespace memory { +int getpagesize(void) { + return util::getPageSize(); +} + +uint64_t getTotalSystemMemory() { + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + + return status.ullTotalPhys; +} + +uintptr_t reserve(uint64_t start, uint64_t size, uint64_t alignment, bool isGpu) { + LOG_USE_MODULE(memory); + + MEM_ADDRESS_REQUIREMENTS requirements { + .LowestStartingAddress = (PVOID)start, + .HighestEndingAddress = (PVOID)0, + .Alignment = alignment, + }; + + MEM_EXTENDED_PARAMETER param { + .Type = MemExtendedParameterAddressRequirements, + .Pointer = &requirements, + }; + + auto ptr = (uintptr_t)getVirtualAlloc2()(GetCurrentProcess(), nullptr, size, isGpu ? MEM_RESERVE | MEM_WRITE_WATCH : MEM_RESERVE, PAGE_NOACCESS, ¶m, 1); + if (ptr == 0) { + auto err = static_cast(GetLastError()); + if (err != ERROR_INVALID_PARAMETER) { + LOG_ERR(L"reserve failed err:0x%08x| start:0x%08llx size:%llu alignment:%llu isGpu:%d", err, start, size, alignment, isGpu); + } + return 0; + } + + return ptr; +} + +uint64_t commit(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment, int prot) { + LOG_USE_MODULE(memory); + + auto ptr = (uintptr_t)VirtualAlloc((LPVOID)(baseAddr + offset), size, MEM_COMMIT, convProtection(prot)); + if (ptr == 0) { + auto err = static_cast(GetLastError()); + LOG_ERR(L"commit failed err:0x%0x| base:0x%08llx offset:0x%08llx size:%llu alignment:%llu prot:%d", err, baseAddr, offset, size, alignment, prot); + } + return ptr; +} + +uint64_t allocGPUMemory(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment) { + LOG_USE_MODULE(memory); + + MEMORY_BASIC_INFORMATION im; + VirtualQuery((LPVOID)baseAddr, &im, sizeof(im)); + + VirtualFree(im.AllocationBase, 0, MEM_RELEASE); + + auto ptrBase = reserve((uint64_t)im.AllocationBase, im.RegionSize, alignment, true); + if (ptrBase == 0) { + auto err = static_cast(GetLastError()); + LOG_ERR(L"allocGPUMemory failed err:0x%0x| addr:0x%08llx size:%llu", err, baseAddr, im.RegionSize); + return 0; + } + + auto ptr = (uintptr_t)VirtualAlloc((LPVOID)(baseAddr + offset), size, MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE); + if (ptr == 0) { + auto err = static_cast(GetLastError()); + LOG_ERR(L"allocGPUMemory failed err:0x%0x| addr:0x%08llx size:%llu", err, baseAddr, size); + return 0; + } + + LOG_DEBUG(L"allocGPUMemory| base:0x%08llx(0x%08llx) addr:0x%08llx", ptrBase, baseAddr, ptr); + return ptr; +} + +int64_t queryAlloc(uintptr_t addr, uintptr_t* start, uintptr_t* end, int* prot) { + MEMORY_BASIC_INFORMATION im; + if (VirtualQuery((LPVOID)addr, &im, sizeof(im)) == 0) { + return -1; + } + + if (start != nullptr) *start = (uintptr_t)im.BaseAddress; + if (end != nullptr) *end = *start + im.RegionSize; + if (prot != nullptr) { + *prot = convProtection(im.Protect); + } + return (int64_t)im.AllocationBase; +} + +uint64_t allocAligned(uint64_t address, uint64_t size, int prot, uint64_t alignment) { + LOG_USE_MODULE(memory); + + constexpr uint64_t USER_MIN = DIRECTMEM_START; + constexpr uint64_t USER_MAX = 0xFBFFFFFFFFu; + + MEM_ADDRESS_REQUIREMENTS req2 {}; + MEM_EXTENDED_PARAMETER param2 {}; + req2.LowestStartingAddress = (address == 0 ? reinterpret_cast(USER_MIN) : reinterpret_cast(address)); + req2.HighestEndingAddress = reinterpret_cast(USER_MAX); + req2.Alignment = alignment; + param2.Type = MemExtendedParameterAddressRequirements; + param2.Pointer = &req2; + + static auto virtual_alloc2 = getVirtualAlloc2(); + + if (virtual_alloc2 == nullptr) { + LOG_CRIT(L"virtual_alloc2 == nullptr"); + return 0; + } + DWORD flags = static_cast(MEM_COMMIT) | static_cast(MEM_RESERVE); + if (checkIsGPU(prot)) flags |= MEM_WRITE_WATCH; + auto ptr = reinterpret_cast(virtual_alloc2(0, nullptr, size, flags, convProtection(prot), ¶m2, 1)); + + if (ptr == 0) { + auto err = static_cast(GetLastError()); + if (err != ERROR_INVALID_PARAMETER) { + LOG_ERR(L"VirtualAlloc2(alignment = 0x%08llx failed: 0x%04x", alignment, err); + } else { + return alloc(0, size, prot); + } + } + return ptr; +} + +uint64_t alloc(uint64_t address, uint64_t size, int prot) { + LOG_USE_MODULE(memory); + + DWORD flags = static_cast(MEM_COMMIT) | static_cast(MEM_RESERVE); + if (checkIsGPU(prot)) flags |= MEM_WRITE_WATCH; + + auto ptr = reinterpret_cast(VirtualAlloc(reinterpret_cast(static_cast(address)), size, flags, convProtection(prot))); + if (ptr == 0) { + auto err = static_cast(GetLastError()); + + if (err != ERROR_INVALID_ADDRESS) { + LOG_ERR(L"VirtualAlloc() failed @0x%08llx:0x%08llx protection:0x%x, err:0x%04x", address, size, prot, err); + } else { + return allocAligned(address, size, prot, 0); + } + } + return ptr; +} + +bool allocFixed(uint64_t address, uint64_t size, int protection) { + LOG_USE_MODULE(memory); + DWORD flags = static_cast(MEM_COMMIT) | static_cast(MEM_RESERVE); + if (checkIsGPU(protection)) flags |= MEM_WRITE_WATCH; + + auto ptr = reinterpret_cast(VirtualAlloc(reinterpret_cast(static_cast(address)), size, flags, convProtection(protection))); + if (ptr == 0) { + auto err = static_cast(GetLastError()); + + LOG_ERR(L"VirtualAlloc() failed: 0x%04x", err); + + return false; + } + + if (ptr != address) { + LOG_ERR(L"VirtualAlloc() failed: wrong address"); + VirtualFree(reinterpret_cast(ptr), 0, MEM_RELEASE); + return false; + } + + return true; +} + +bool free(uint64_t address) { + LOG_USE_MODULE(memory); + if (VirtualFree(reinterpret_cast(static_cast(address)), 0, MEM_RELEASE) == 0) { + LOG_ERR(L"VirtualFree() failed: 0x%04x", static_cast(GetLastError())); + return false; + } + return true; +} + +bool protect(uint64_t address, uint64_t size, int protection, int* oldProt) { + LOG_USE_MODULE(memory); + + DWORD oldProtection; + if (VirtualProtect(reinterpret_cast(static_cast(address)), size, convProtection(protection), &oldProtection) == 0) { + LOG_ERR(L"VirtualProtect() failed addr:0x%08llx size:0x%08llx prot:%d err:0x%04x", address, size, protection, static_cast(GetLastError())); + return false; + } + + if (oldProt != nullptr) *oldProt = (protection & 0xF0) | convProtection(oldProtection); + return true; +} + +int getProtection(uint64_t address) { + LOG_USE_MODULE(memory); + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery((const void*)address, &mbi, sizeof(mbi))) { + LOG_ERR(L"Failed to query memory"); + return 0; + } + return convProtection(mbi.Protect); +} + +void installHook_long(uintptr_t dst, uintptr_t src, _t_hook& pGateway, size_t lenOpCodes) { + std::array codeGateway = { + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // addr + }; + + // setup jmp back code + *(uint64_t*)&codeGateway[6] = (uint64_t)(src + lenOpCodes); + + LOG_USE_MODULE(memory); + { + // Interceptor, jmp to dst + std::array code = codeGateway; + *(uint64_t*)&code[6] = (uint64_t)(dst); + + int oldMode; + protect(src, code.size(), SceProtExecute | SceProtRead | SceProtWrite, &oldMode); + + memcpy(pGateway.data.data(), (uint8_t*)src, lenOpCodes); // save code -> add to gateway + memcpy((uint8_t*)src, code.data(), code.size()); + + if (::FlushInstructionCache(GetCurrentProcess(), (void*)src, code.size()) == 0) { + LOG_ERR(L"FlushInstructionCache() failed: 0x%04x", static_cast(GetLastError())); + return; + } + protect(src, code.size(), oldMode); + // - interceptor + } + + memcpy(pGateway.data.data() + lenOpCodes, (uint8_t*)codeGateway.data(), codeGateway.size()); // cpy to gateway + + protect((uintptr_t)pGateway.data.data(), pGateway.data.size(), SceProtExecute | SceProtRead, nullptr); +} + +int VirtualLock::check_mmaped(void* addr, size_t len) { + return 0; +} + +VirtualLock& VirtualLock::instance() { + static VirtualLock instance; + return instance; +} + +void VirtualLock::lock() { + MMLock.lock(); +} + +void VirtualLock::unlock() { + MMLock.unlock(); +} + +void MLOCK(VirtualLock& vLock) { + vLock.lock(); +} + +void MUNLOCK(VirtualLock& vLock) { + vLock.unlock(); +} + +int check_mmaped(void* addr, size_t len) { + LOG_USE_MODULE(memory); + VirtualLock& vLock = VirtualLock::instance(); + MLOCK(vLock); + + int result = vLock.check_mmaped(addr, len); + + MUNLOCK(vLock); + return result; +} +} // namespace memory \ No newline at end of file diff --git a/core/memory/memory.h b/core/memory/memory.h new file mode 100644 index 00000000..81c65ed0 --- /dev/null +++ b/core/memory/memory.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include + +constexpr int SceProtRead = 1; +constexpr int SceProtWrite = 2; +constexpr int SceProtExecute = 4; + +constexpr uint64_t DIRECTMEM_START = 0x100000000u; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +namespace memory { +constexpr inline bool isExecute(int prot) { + return (prot & 0x4) > 0; +} + +struct _t_hook { + std::array data; // Should be enough for inserting the hook (min 14 max 14+8) +}; + +class VirtualLock { + public: + int check_mmaped(void* addr, size_t len); + static VirtualLock& instance(); + void lock(); + void unlock(); + + private: + std::mutex MMLock; +}; + +void MLOCK(VirtualLock& vLock); +void MUNLOCK(VirtualLock& vLock); + +__APICALL int getpagesize(void); +__APICALL uint64_t getTotalSystemMemory(); +__APICALL uintptr_t reserve(uint64_t start, uint64_t size, uint64_t alignment, bool isGpu); +__APICALL uint64_t commit(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment, int prot); +__APICALL uint64_t allocGPUMemory(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment); +__APICALL int64_t queryAlloc(uintptr_t addr, uintptr_t* start, uintptr_t* end, int* prot); +__APICALL uint64_t alloc(uint64_t address, uint64_t size, int prot); +__APICALL uint64_t allocAligned(uint64_t address, uint64_t size, int prot, uint64_t alignment); +__APICALL bool allocFixed(uint64_t address, uint64_t size, int prot); +__APICALL bool free(uint64_t address); +__APICALL bool protect(uint64_t address, uint64_t size, int prot, int* oldMode = nullptr); +__APICALL int getProtection(uint64_t address); +__APICALL int check_mmaped(void* addr, size_t len); + +__APICALL void installHook_long(uintptr_t dst, uintptr_t src, _t_hook& pGateway, size_t lenOpCodes); +} // namespace memory + +#undef __APICALL \ No newline at end of file diff --git a/core/networking/CMakeLists.txt b/core/networking/CMakeLists.txt new file mode 100644 index 00000000..454e9a71 --- /dev/null +++ b/core/networking/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(networking OBJECT + states/offline.cpp + + states/online/netctl.cpp + states/online/resolver.cpp + states/online/socket.cpp + states/online/epoll.cpp + states/online/http.cpp + + networking.cpp +) + +add_dependencies(networking third_party psOff_utility config_emu) + +target_compile_definitions(networking PUBLIC WIN32_LEAN_AND_MEAN _WINSOCK_DEPRECATED_NO_WARNINGS) + +file(COPY networking.h DESTINATION ${DST_INCLUDE_DIR}/networking) \ No newline at end of file diff --git a/core/networking/networking.cpp b/core/networking/networking.cpp new file mode 100644 index 00000000..17c4f312 --- /dev/null +++ b/core/networking/networking.cpp @@ -0,0 +1,57 @@ +#define __APICALL_EXTERN +#include "networking.h" +#undef __APICALL_EXTERN +#include "config_emu.h" +#include "logging.h" +#include "states/offline.h" +#include "states/online.h" + +LOG_DEFINE_MODULE(NetworkingCore); + +static thread_local int32_t g_net_errno = 0; + +INetworking::INetworking() { + LOG_USE_MODULE(NetworkingCore); + WSADATA data = {}; + if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR) LOG_CRIT(L"WSAStartup failed: %d", getLastError()); +} + +INetworking::~INetworking() { + LOG_USE_MODULE(NetworkingCore); + if (WSACleanup() == SOCKET_ERROR) LOG_CRIT(L"WSACleanup failed: %d", getLastError()); +} + +int32_t INetworking::getLastError() { + auto win_err = (uint32_t)GetLastError(); + if (win_err == WSANOTINITIALISED) return Err::Net::ERROR_ENOTINIT; + return (0x80000000 | (0x041 << 16) | (0x0100 | (win_err - 10000))); +} + +int32_t* INetworking::getErrnoPtr() { + return &g_net_errno; +} + +class NetInitializer { + INetworking* m_itf; + + public: + NetInitializer() { + auto [lock, jData] = accessConfig()->accessModule(ConfigModFlag::GENERAL); + bool state = false; + + if (getJsonParam(jData, "netEnabled", state) && state == true) { + static OnlineNet ion; + m_itf = &ion; + } else { + static OfflineNet oon; + m_itf = &oon; + } + } + + INetworking& getClass() { return (*m_itf); } +}; + +INetworking& accessNetworking() { + static NetInitializer ni; + return ni.getClass(); +} diff --git a/core/networking/networking.h b/core/networking/networking.h new file mode 100644 index 00000000..a5b7c759 --- /dev/null +++ b/core/networking/networking.h @@ -0,0 +1,165 @@ +#pragma once + +#include "modules/libSceHttp/httpsTypes.h" +#include "modules/libSceHttp/types.h" +#include "modules/libSceNet/resolverTypes.h" +#include "modules/libSceNet/socketTypes.h" +#include "modules/libSceNet/types.h" +#include "modules/libSceNetCtl/types.h" +#include "modules/libSceSsl/types.h" +#include "modules_include/common.h" +#include "utility/utility.h" + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +class INetworking { + CLASS_NO_COPY(INetworking); + CLASS_NO_MOVE(INetworking); + + protected: + INetworking(); + + public: + ~INetworking(); + + /** + * @brief returns current network error code + * + * @return int32_t + */ + __APICALL static int32_t getLastError(); + + /** + * @brief returns errno ptr + * + * @return int32_t* + */ + __APICALL static int32_t* getErrnoPtr(); + + /* SceNetCtl facility*/ + + /** + * @brief returns information about specified network parameter + * + * @param code + * @param info + * @return int32_t + */ + virtual int32_t netCtlGetInfo(int32_t code, SceNetCtlInfo* info) = 0; + virtual int32_t netCtlGetState(int32_t* state) = 0; + + /* SceNet facility */ + + /* Resolver sub-facility */ + virtual SceNetId resolverCreate(const char* name, int memid, int flags) = 0; + virtual int32_t resolverStartNtoa(SceNetId rid, const char* hostname, SceNetInAddr_t* addr, int timeout, int retries, int flags) = 0; + virtual int32_t resolverStartAton(SceNetId rid, const SceNetInAddr_t* addr, char* hostname, int len, int timeout, int retry, int flags) = 0; + virtual int32_t resolverStartNtoaMultipleRecords(SceNetId rid, const char* hostname, SceNetResolverInfo* info, int timeout, int retries, int flags) = 0; + virtual int32_t resolverGetError(SceNetId rid, int* result) = 0; + virtual int32_t resolverDestroy(SceNetId rid) = 0; + virtual int32_t resolverAbort(SceNetId rid, int flags) = 0; + + /* Epoll sub-facility */ + virtual SceNetId epollCreate(const char* name, int flags) = 0; + virtual int epollControl(SceNetId eid, int op, SceNetId id, SceNetEpollEvent* event) = 0; + virtual int epollWait(SceNetId eid, SceNetEpollEvent* events, int maxevents, int timeout) = 0; + virtual int epollDestroy(SceNetId eid) = 0; + virtual int epollAbort(SceNetId eid, int flags) = 0; + + /* Socket sub-facility */ + virtual SceNetId socketCreate(const char* name, int family, int type, int protocol) = 0; + virtual SceNetId socketAccept(SceNetId s, SceNetSockaddr* addr, SceNetSocklen_t* addrlen) = 0; + virtual int socketBind(SceNetId s, const SceNetSockaddr* addr, SceNetSocklen_t addrlen) = 0; + virtual int socketConnect(SceNetId s, const SceNetSockaddr* name, SceNetSocklen_t namelen) = 0; + virtual int socketGetpeername(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) = 0; + virtual int socketGetsockname(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) = 0; + virtual int socketGetsockopt(SceNetId s, int level, int optname, void* optval, SceNetSocklen_t* optlen) = 0; + virtual int socketListen(SceNetId s, int backlog) = 0; + virtual int socketRecv(SceNetId s, void* buf, size_t len, int flags) = 0; + virtual int socketRecvfrom(SceNetId s, void* buf, size_t len, int flags, SceNetSockaddr* from, SceNetSocklen_t* fromlen) = 0; + virtual int socketRecvmsg(SceNetId s, SceNetMsghdr* msg, int flags) = 0; + virtual int socketSend(SceNetId s, const void* msg, size_t len, int flags) = 0; + virtual int socketSendto(SceNetId s, const void* msg, size_t len, int flags, const SceNetSockaddr* to, SceNetSocklen_t tolen) = 0; + virtual int socketSendmsg(SceNetId s, const SceNetMsghdr* msg, int flags) = 0; + virtual int socketSetsockopt(SceNetId s, int level, int optname, const void* optval, SceNetSocklen_t optlen) = 0; + virtual int socketShutdown(SceNetId s, int how) = 0; + virtual int socketClose(SceNetId s) = 0; + virtual int socketAbort(SceNetId s, int flags) = 0; + + /* HTTP facility */ + + /* HTTP1 facility */ + virtual int httpInit(int libnetMemId, int libsslCtxId, size_t poolSize) = 0; + virtual int httpTerm(int libhttpCtxId) = 0; + virtual int httpGetMemoryPoolStats(int libhttpCtxId, SceHttpMemoryPoolStats* currentStat) = 0; + virtual int httpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer, int isAutoProxyConf) = 0; + virtual int httpDeleteTemplate(int tmplId) = 0; + virtual int httpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive) = 0; + virtual int httpCreateConnectionWithURL(int tmplId, const char* url, int isEnableKeepalive) = 0; + virtual int httpDeleteConnection(int connId) = 0; + virtual int httpCreateRequest(int connId, int method, const char* path, uint64_t contentLength) = 0; + virtual int httpCreateRequest2(int connId, const char* method, const char* path, uint64_t contentLength) = 0; + virtual int httpCreateRequestWithURL(int connId, int method, const char* url, uint64_t contentLength) = 0; + virtual int httpCreateRequestWithURL2(int connId, const char* method, const char* url, uint64_t contentLength) = 0; + virtual int httpDeleteRequest(int reqId) = 0; + virtual int httpSetRequestContentLength(int id, uint64_t contentLength) = 0; + virtual int httpSetChunkedTransferEnabled(int id, int isEnable) = 0; + virtual int httpSetInflateGZIPEnabled(int id, int isEnable) = 0; + virtual int httpSendRequest(int reqId, const void* postData, size_t size) = 0; + virtual int httpAbortRequest(int reqId) = 0; + virtual int httpGetResponseContentLength(int reqId, int* result, uint64_t* contentLength) = 0; + virtual int httpGetStatusCode(int reqId, int* statusCode) = 0; + virtual int httpGetAllResponseHeaders(int reqId, char** header, size_t* headerSize) = 0; + virtual int httpReadData(int reqId, void* data, size_t size) = 0; + virtual int httpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode) = 0; + virtual int httpRemoveRequestHeader(int id, const char* name) = 0; + virtual int httpParseResponseHeader(const char* header, size_t headerLen, const char* fieldStr, const char** fieldValue, size_t* valueLen) = 0; + virtual int httpParseStatusLine(const char* statusLine, size_t lineLen, int* httpMajorVer, int* httpMinorVer, int* responseCode, const char** reasonPhrase, + size_t* phraseLen) = 0; + virtual int httpSetResponseHeaderMaxSize(int id, size_t headerSize) = 0; + virtual int httpSetAuthInfoCallback(int id, SceHttpAuthInfoCallback cbfunc, void* userArg) = 0; + virtual int httpSetAuthEnabled(int id, int isEnable) = 0; + virtual int httpGetAuthEnabled(int id, int* isEnable) = 0; + virtual int httpAuthCacheFlush(int libhttpCtxId) = 0; + virtual int httpSetRedirectCallback(int id, SceHttpRedirectCallback cbfunc, void* userArg) = 0; + virtual int httpSetAutoRedirect(int id, int isEnable) = 0; + virtual int httpGetAutoRedirect(int id, int* isEnable) = 0; + virtual int httpRedirectCacheFlush(int libhttpCtxId) = 0; + virtual int httpSetResolveTimeOut(int id, uint32_t usec) = 0; + virtual int httpSetResolveRetry(int id, int retry) = 0; + virtual int httpSetConnectTimeOut(int id, uint32_t usec) = 0; + virtual int httpSetSendTimeOut(int id, uint32_t usec) = 0; + virtual int httpSetRecvTimeOut(int id, uint32_t usec) = 0; + virtual int httpSetRequestStatusCallback(int id, SceHttpRequestStatusCallback cbfunc, void* userArg) = 0; + virtual int httpGetLastErrno(int reqId, int* errNum) = 0; + virtual int httpSetNonblock(int id, int isEnable) = 0; + virtual int httpGetNonblock(int id, int* isEnable) = 0; + virtual int httpTrySetNonblock(int id, int isEnable) = 0; + virtual int httpTryGetNonblock(int id, int* isEnable) = 0; + virtual int httpCreateEpoll(int libhttpCtxId, SceHttpEpollHandle* eh) = 0; + virtual int httpSetEpoll(int id, SceHttpEpollHandle eh, void* userArg) = 0; + virtual int httpUnsetEpoll(int id) = 0; + virtual int httpGetEpoll(int id, SceHttpEpollHandle* eh, void** userArg) = 0; + virtual int httpDestroyEpoll(int libhttpCtxId, SceHttpEpollHandle eh) = 0; + virtual int httpWaitRequest(SceHttpEpollHandle eh, SceHttpNBEvent* nbev, int maxevents, int timeout) = 0; + virtual int httpAbortWaitRequest(SceHttpEpollHandle eh) = 0; + + // HTTPS + virtual int httpsLoadCert(int libhttpCtxId, int caCertNum, const SceSslData** caList, const SceSslData* cert, const SceSslData* privKey) = 0; + virtual int httpsUnloadCert(int libhttpCtxId) = 0; + virtual int httpsEnableOption(int id, uint32_t sslFlags) = 0; + virtual int httpsDisableOption(int id, uint32_t sslFlags) = 0; + virtual int httpsGetSslError(int id, int* errNum, uint32_t* detail) = 0; + virtual int httpsSetSslCallback(int id, SceHttpsCallback cbfunc, void* userArg) = 0; + virtual int httpsSetSslVersion(int id, SceSslVersion version) = 0; +}; + + +__APICALL INetworking& accessNetworking(); +#undef __APICALL diff --git a/core/networking/states/offline.cpp b/core/networking/states/offline.cpp new file mode 100644 index 00000000..28b5b258 --- /dev/null +++ b/core/networking/states/offline.cpp @@ -0,0 +1,416 @@ +#include "offline.h" + +#include "logging.h" +#include "modules/libSceNetCtl/codes.h" + +LOG_DEFINE_MODULE(OfflineNetworkingCore); + +int32_t OfflineNet::netCtlGetInfo(int32_t code, SceNetCtlInfo* info) { + LOG_USE_MODULE(OfflineNetworkingCore); + LOG_TRACE(L"netCtlGetInfo(%d, %p)", code, info); + + switch (code) { + case 1: info->device = 0; break; + case 2: memset(info->ether_addr.data, 0, SCE_NET_ETHER_ADDR_LEN); break; + case 3: info->mtu = 0; break; + case 4: info->link = 0; break; + case 5: memset(info->bssid.data, 0, SCE_NET_ETHER_ADDR_LEN); break; + case 6: *info->ssid = '\0'; break; + case 7: info->wifi_security = 0; break; + case 8: info->rssi_dbm = 0; break; + case 9: info->rssi_percentage = 0; break; + case 10: info->channel = 0; break; + case 11: info->ip_config = 0; break; + case 12: *info->dhcp_hostname = '\0'; break; + case 13: *info->pppoe_auth_name = '\0'; break; + case 14: *info->ip_address = '\0'; break; + case 15: *info->netmask = '\0'; break; + case 16: *info->default_route = '\0'; break; + case 17: *info->primary_dns = '\0'; break; + case 18: *info->secondary_dns = '\0'; break; + case 19: info->http_proxy_config = 0; break; + case 20: *info->http_proxy_server = '\0'; break; + case 21: info->http_proxy_port = 0; break; + } + + return Err::NetCtl::NOT_CONNECTED; +} + +int32_t OfflineNet::netCtlGetState(int32_t* state) { + *state = 0; // Network disconnected + return Ok; +} + +SceNetId OfflineNet::resolverCreate(const char* name, int memid, int flags) { + static SceNetId id = 0; + return ++id; +} + +int32_t OfflineNet::resolverStartNtoa(SceNetId rid, const char* hostname, SceNetInAddr_t* addr, int timeout, int retries, int flags) { + return getErr(ErrCode::_ETIMEDOUT); +} + +int32_t OfflineNet::resolverStartAton(SceNetId rid, const SceNetInAddr_t* addr, char* hostname, int len, int timeout, int retry, int flags) { + return getErr(ErrCode::_ETIMEDOUT); +} + +int32_t OfflineNet::resolverStartNtoaMultipleRecords(SceNetId rid, const char* hostname, SceNetResolverInfo* info, int timeout, int retries, int flags) { + return getErr(ErrCode::_ETIMEDOUT); +} + +int32_t OfflineNet::resolverGetError(SceNetId rid, int* result) { + *result = (int)ErrCode::_ETIMEDOUT; + return Ok; +} + +int32_t OfflineNet::resolverDestroy(SceNetId rid) { + return Ok; +} + +int32_t OfflineNet::resolverAbort(SceNetId rid, int flags) { + return Ok; +} + +SceNetId OfflineNet::epollCreate(const char* name, int flags) { + static SceNetId id = 0; + return ++id; +} + +int32_t OfflineNet::epollControl(SceNetId eid, int op, SceNetId id, SceNetEpollEvent* event) { + return Ok; +} + +int32_t OfflineNet::epollWait(SceNetId eid, SceNetEpollEvent* events, int maxevents, int timeout) { + return Ok; +} + +int32_t OfflineNet::epollDestroy(SceNetId eid) { + return Ok; +} + +int32_t OfflineNet::epollAbort(SceNetId eid, int flags) { + return Ok; +} + +SceNetId OfflineNet::socketCreate(const char* name, int family, int type, int protocol) { + *INetworking::getErrnoPtr() = NetErrNo::SCE_NET_EPROTONOSUPPORT; + return Err::Net::ERROR_EINVAL; +} + +SceNetId OfflineNet::socketAccept(SceNetId s, SceNetSockaddr* addr, SceNetSocklen_t* addrlen) { + return 0; +} + +int OfflineNet::socketBind(SceNetId s, const SceNetSockaddr* addr, SceNetSocklen_t addrlen) { + return Ok; +} + +int OfflineNet::socketConnect(SceNetId s, const SceNetSockaddr* name, SceNetSocklen_t namelen) { + return Ok; +} + +int OfflineNet::socketGetpeername(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) { + return Ok; +} + +int OfflineNet::socketGetsockname(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) { + return Ok; +} + +int OfflineNet::socketGetsockopt(SceNetId s, int level, int optname, void* optval, SceNetSocklen_t* optlen) { + return 0; +} + +int OfflineNet::socketListen(SceNetId s, int backlog) { + return Ok; +} + +int OfflineNet::socketRecv(SceNetId s, void* buf, size_t len, int flags) { + return 0; +} + +int OfflineNet::socketRecvfrom(SceNetId s, void* buf, size_t len, int flags, SceNetSockaddr* from, SceNetSocklen_t* fromlen) { + return 0; +} + +int OfflineNet::socketRecvmsg(SceNetId s, SceNetMsghdr* msg, int flags) { + return Ok; +} + +int OfflineNet::socketSend(SceNetId s, const void* msg, size_t len, int flags) { + return 0; +} + +int OfflineNet::socketSendto(SceNetId s, const void* msg, size_t len, int flags, const SceNetSockaddr* to, SceNetSocklen_t tolen) { + return 0; +} + +int OfflineNet::socketSendmsg(SceNetId s, const SceNetMsghdr* msg, int flags) { + return Ok; +} + +int OfflineNet::socketSetsockopt(SceNetId s, int level, int optname, const void* optval, SceNetSocklen_t optlen) { + return Ok; +} + +int OfflineNet::socketShutdown(SceNetId s, int how) { + return Ok; +} + +int OfflineNet::socketClose(SceNetId s) { + return Ok; +} + +int OfflineNet::socketAbort(SceNetId s, int flags) { + return Ok; +} + +int OfflineNet::httpInit(int libnetMemId, int libsslCtxId, size_t poolSize) { + return Ok; +} + +int OfflineNet::httpTerm(int libhttpCtxId) { + return Ok; +} + +int OfflineNet::httpGetMemoryPoolStats(int libhttpCtxId, SceHttpMemoryPoolStats* currentStat) { + return Ok; +} + +int OfflineNet::httpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer, int isAutoProxyConf) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpDeleteTemplate(int tmplId) { + return Ok; +} + +int OfflineNet::httpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpCreateConnectionWithURL(int tmplId, const char* url, int isEnableKeepalive) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpDeleteConnection(int connId) { + return Ok; +} + +int OfflineNet::httpCreateRequest(int connId, int method, const char* path, uint64_t contentLength) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpCreateRequest2(int connId, const char* method, const char* path, uint64_t contentLength) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpCreateRequestWithURL(int connId, int method, const char* url, uint64_t contentLength) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpCreateRequestWithURL2(int connId, const char* method, const char* url, uint64_t contentLength) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpDeleteRequest(int reqId) { + return Ok; +} + +int OfflineNet::httpSetRequestContentLength(int id, uint64_t contentLength) { + return Ok; +} + +int OfflineNet::httpSetChunkedTransferEnabled(int id, int isEnable) { + return Ok; +} + +int OfflineNet::httpSetInflateGZIPEnabled(int id, int isEnable) { + return Ok; +} + +int OfflineNet::httpSendRequest(int reqId, const void* postData, size_t size) { + return Ok; +} + +int OfflineNet::httpAbortRequest(int reqId) { + return Ok; +} + +int OfflineNet::httpGetResponseContentLength(int reqId, int* result, uint64_t* contentLength) { + return Ok; +} + +int OfflineNet::httpGetStatusCode(int reqId, int* statusCode) { + return Ok; +} + +int OfflineNet::httpGetAllResponseHeaders(int reqId, char** header, size_t* headerSize) { + return Ok; +} + +int OfflineNet::httpReadData(int reqId, void* data, size_t size) { + return Ok; +} + +int OfflineNet::httpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode) { + return Ok; +} + +int OfflineNet::httpRemoveRequestHeader(int id, const char* name) { + return Ok; +} + +int OfflineNet::httpParseResponseHeader(const char* header, size_t headerLen, const char* fieldStr, const char** fieldValue, size_t* valueLen) { + return Ok; +} + +int OfflineNet::httpParseStatusLine(const char* statusLine, size_t lineLen, int* httpMajorVer, int* httpMinorVer, int* responseCode, const char** reasonPhrase, + size_t* phraseLen) { + return Ok; +} + +int OfflineNet::httpSetResponseHeaderMaxSize(int id, size_t headerSize) { + return Ok; +} + +int OfflineNet::httpSetAuthInfoCallback(int id, SceHttpAuthInfoCallback cbfunc, void* userArg) { + return Ok; +} + +int OfflineNet::httpSetAuthEnabled(int id, int isEnable) { + return Ok; +} + +int OfflineNet::httpGetAuthEnabled(int id, int* isEnable) { + return Ok; +} + +int OfflineNet::httpAuthCacheFlush(int libhttpCtxId) { + return Ok; +} + +int OfflineNet::httpSetRedirectCallback(int id, SceHttpRedirectCallback cbfunc, void* userArg) { + return Ok; +} + +int OfflineNet::httpSetAutoRedirect(int id, int isEnable) { + return Ok; +} + +int OfflineNet::httpGetAutoRedirect(int id, int* isEnable) { + return Ok; +} + +int OfflineNet::httpRedirectCacheFlush(int libhttpCtxId) { + return Ok; +} + +int OfflineNet::httpSetResolveTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OfflineNet::httpSetResolveRetry(int id, int retry) { + return Ok; +} + +int OfflineNet::httpSetConnectTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OfflineNet::httpSetSendTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OfflineNet::httpSetRecvTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OfflineNet::httpSetRequestStatusCallback(int id, SceHttpRequestStatusCallback cbfunc, void* userArg) { + return Ok; +} + +int OfflineNet::httpGetLastErrno(int reqId, int* errNum) { + return Ok; +} + +int OfflineNet::httpSetNonblock(int id, int isEnable) { + return Ok; +} + +int OfflineNet::httpGetNonblock(int id, int* isEnable) { + return Ok; +} + +int OfflineNet::httpTrySetNonblock(int id, int isEnable) { + return Ok; +} + +int OfflineNet::httpTryGetNonblock(int id, int* isEnable) { + return Ok; +} + +int OfflineNet::httpCreateEpoll(int libhttpCtxId, SceHttpEpollHandle* eh) { + static int id = 0; + return ++id; +} + +int OfflineNet::httpSetEpoll(int id, SceHttpEpollHandle eh, void* userArg) { + return Ok; +} + +int OfflineNet::httpUnsetEpoll(int id) { + return Ok; +} + +int OfflineNet::httpGetEpoll(int id, SceHttpEpollHandle* eh, void** userArg) { + return Ok; +} + +int OfflineNet::httpDestroyEpoll(int libhttpCtxId, SceHttpEpollHandle eh) { + return Ok; +} + +int OfflineNet::httpWaitRequest(SceHttpEpollHandle eh, SceHttpNBEvent* nbev, int maxevents, int timeout) { + return Ok; +} + +int OfflineNet::httpAbortWaitRequest(SceHttpEpollHandle eh) { + return Ok; +} + +// HTTPS +int OfflineNet::httpsLoadCert(int libhttpCtxId, int caCertNum, const SceSslData** caList, const SceSslData* cert, const SceSslData* privKey) { + return Ok; +} + +int OfflineNet::httpsUnloadCert(int libhttpCtxId) { + return Ok; +} + +int OfflineNet::httpsEnableOption(int id, uint32_t sslFlags) { + return Ok; +} + +int OfflineNet::httpsDisableOption(int id, uint32_t sslFlags) { + return Ok; +} + +int OfflineNet::httpsGetSslError(int id, int* errNum, uint32_t* detail) { + return Ok; +} + +int OfflineNet::httpsSetSslCallback(int id, SceHttpsCallback cbfunc, void* userArg) { + return Ok; +} + +int OfflineNet::httpsSetSslVersion(int id, SceSslVersion version) { + return Ok; +} diff --git a/core/networking/states/offline.h b/core/networking/states/offline.h new file mode 100644 index 00000000..cbd0d50d --- /dev/null +++ b/core/networking/states/offline.h @@ -0,0 +1,122 @@ +#pragma once +#include "../networking.h" + +class OfflineNet: public INetworking { + public: + /* SceNetCtl facility*/ + + /** + * @brief gets information about network parameters + * + * @param code + * @param info + * @return int32_t + */ + int32_t netCtlGetInfo(int32_t code, SceNetCtlInfo* info) final; + int32_t netCtlGetState(int32_t* state) final; + + /* SceNet facility */ + + /* Resolver sub-facility */ + SceNetId resolverCreate(const char* name, int memid, int flags) final; + int32_t resolverStartNtoa(SceNetId rid, const char* hostname, SceNetInAddr_t* addr, int timeout, int retries, int flags) final; + int32_t resolverStartAton(SceNetId rid, const SceNetInAddr_t* addr, char* hostname, int len, int timeout, int retry, int flags) final; + int32_t resolverStartNtoaMultipleRecords(SceNetId rid, const char* hostname, SceNetResolverInfo* info, int timeout, int retries, int flags) final; + int32_t resolverGetError(SceNetId rid, int* result) final; + int32_t resolverDestroy(SceNetId rid) final; + int32_t resolverAbort(SceNetId rid, int flags) final; + + /* Epoll sub-facility */ + SceNetId epollCreate(const char* name, int flags) final; + int32_t epollControl(SceNetId eid, int op, SceNetId id, SceNetEpollEvent* event) final; + int32_t epollWait(SceNetId eid, SceNetEpollEvent* events, int maxevents, int timeout) final; + int32_t epollDestroy(SceNetId eid) final; + int32_t epollAbort(SceNetId eid, int flags) final; + + /* Socket sub-facility */ + SceNetId socketCreate(const char* name, int family, int type, int protocol) final; + SceNetId socketAccept(SceNetId s, SceNetSockaddr* addr, SceNetSocklen_t* addrlen) final; + int socketBind(SceNetId s, const SceNetSockaddr* addr, SceNetSocklen_t addrlen) final; + int socketConnect(SceNetId s, const SceNetSockaddr* name, SceNetSocklen_t namelen) final; + int socketGetpeername(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) final; + int socketGetsockname(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) final; + int socketGetsockopt(SceNetId s, int level, int optname, void* optval, SceNetSocklen_t* optlen) final; + int socketListen(SceNetId s, int backlog) final; + int socketRecv(SceNetId s, void* buf, size_t len, int flags) final; + int socketRecvfrom(SceNetId s, void* buf, size_t len, int flags, SceNetSockaddr* from, SceNetSocklen_t* fromlen) final; + int socketRecvmsg(SceNetId s, SceNetMsghdr* msg, int flags) final; + int socketSend(SceNetId s, const void* msg, size_t len, int flags) final; + int socketSendto(SceNetId s, const void* msg, size_t len, int flags, const SceNetSockaddr* to, SceNetSocklen_t tolen) final; + int socketSendmsg(SceNetId s, const SceNetMsghdr* msg, int flags) final; + int socketSetsockopt(SceNetId s, int level, int optname, const void* optval, SceNetSocklen_t optlen) final; + int socketShutdown(SceNetId s, int how) final; + int socketClose(SceNetId s) final; + int socketAbort(SceNetId s, int flags) final; + + /* HTTP facility */ + + /* HTTP1 facility */ + int httpInit(int libnetMemId, int libsslCtxId, size_t poolSize) final; + int httpTerm(int libhttpCtxId) final; + int httpGetMemoryPoolStats(int libhttpCtxId, SceHttpMemoryPoolStats* currentStat) final; + int httpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer, int isAutoProxyConf) final; + int httpDeleteTemplate(int tmplId) final; + int httpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive) final; + int httpCreateConnectionWithURL(int tmplId, const char* url, int isEnableKeepalive) final; + int httpDeleteConnection(int connId) final; + int httpCreateRequest(int connId, int method, const char* path, uint64_t contentLength) final; + int httpCreateRequest2(int connId, const char* method, const char* path, uint64_t contentLength) final; + int httpCreateRequestWithURL(int connId, int method, const char* url, uint64_t contentLength) final; + int httpCreateRequestWithURL2(int connId, const char* method, const char* url, uint64_t contentLength) final; + int httpDeleteRequest(int reqId) final; + int httpSetRequestContentLength(int id, uint64_t contentLength) final; + int httpSetChunkedTransferEnabled(int id, int isEnable) final; + int httpSetInflateGZIPEnabled(int id, int isEnable) final; + int httpSendRequest(int reqId, const void* postData, size_t size) final; + int httpAbortRequest(int reqId) final; + int httpGetResponseContentLength(int reqId, int* result, uint64_t* contentLength) final; + int httpGetStatusCode(int reqId, int* statusCode) final; + int httpGetAllResponseHeaders(int reqId, char** header, size_t* headerSize) final; + int httpReadData(int reqId, void* data, size_t size) final; + int httpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode) final; + int httpRemoveRequestHeader(int id, const char* name) final; + int httpParseResponseHeader(const char* header, size_t headerLen, const char* fieldStr, const char** fieldValue, size_t* valueLen) final; + int httpParseStatusLine(const char* statusLine, size_t lineLen, int* httpMajorVer, int* httpMinorVer, int* responseCode, const char** reasonPhrase, + size_t* phraseLen) final; + int httpSetResponseHeaderMaxSize(int id, size_t headerSize) final; + int httpSetAuthInfoCallback(int id, SceHttpAuthInfoCallback cbfunc, void* userArg) final; + int httpSetAuthEnabled(int id, int isEnable) final; + int httpGetAuthEnabled(int id, int* isEnable) final; + int httpAuthCacheFlush(int libhttpCtxId) final; + int httpSetRedirectCallback(int id, SceHttpRedirectCallback cbfunc, void* userArg) final; + int httpSetAutoRedirect(int id, int isEnable) final; + int httpGetAutoRedirect(int id, int* isEnable) final; + int httpRedirectCacheFlush(int libhttpCtxId) final; + int httpSetResolveTimeOut(int id, uint32_t usec) final; + int httpSetResolveRetry(int id, int retry) final; + int httpSetConnectTimeOut(int id, uint32_t usec) final; + int httpSetSendTimeOut(int id, uint32_t usec) final; + int httpSetRecvTimeOut(int id, uint32_t usec) final; + int httpSetRequestStatusCallback(int id, SceHttpRequestStatusCallback cbfunc, void* userArg) final; + int httpGetLastErrno(int reqId, int* errNum) final; + int httpSetNonblock(int id, int isEnable) final; + int httpGetNonblock(int id, int* isEnable) final; + int httpTrySetNonblock(int id, int isEnable) final; + int httpTryGetNonblock(int id, int* isEnable) final; + int httpCreateEpoll(int libhttpCtxId, SceHttpEpollHandle* eh) final; + int httpSetEpoll(int id, SceHttpEpollHandle eh, void* userArg) final; + int httpUnsetEpoll(int id) final; + int httpGetEpoll(int id, SceHttpEpollHandle* eh, void** userArg) final; + int httpDestroyEpoll(int libhttpCtxId, SceHttpEpollHandle eh) final; + int httpWaitRequest(SceHttpEpollHandle eh, SceHttpNBEvent* nbev, int maxevents, int timeout) final; + int httpAbortWaitRequest(SceHttpEpollHandle eh) final; + + // HTTPS + int httpsLoadCert(int libhttpCtxId, int caCertNum, const SceSslData** caList, const SceSslData* cert, const SceSslData* privKey) final; + int httpsUnloadCert(int libhttpCtxId) final; + int httpsEnableOption(int id, uint32_t sslFlags) final; + int httpsDisableOption(int id, uint32_t sslFlags) final; + int httpsGetSslError(int id, int* errNum, uint32_t* detail) final; + int httpsSetSslCallback(int id, SceHttpsCallback cbfunc, void* userArg) final; + int httpsSetSslVersion(int id, SceSslVersion version) final; +}; diff --git a/core/networking/states/online.h b/core/networking/states/online.h new file mode 100644 index 00000000..0c4915bf --- /dev/null +++ b/core/networking/states/online.h @@ -0,0 +1,126 @@ +#pragma once +#include "../networking.h" + +#include +#include +#include + +class OnlineNet: public INetworking { + public: + /* SceNetCtl facility*/ + + /** + * @brief gets information about network parameters + * + * @param code + * @param info + * @return int32_t + */ + int32_t netCtlGetInfo(int32_t code, SceNetCtlInfo* info) final; + int32_t netCtlGetState(int32_t* state) final; + + /* SceNet facility */ + + /* Resolver sub-facility */ + SceNetId resolverCreate(const char* name, int memid, int flags) final; + int32_t resolverStartNtoa(SceNetId rid, const char* hostname, SceNetInAddr_t* addr, int timeout, int retries, int flags) final; + int32_t resolverStartAton(SceNetId rid, const SceNetInAddr_t* addr, char* hostname, int len, int timeout, int retry, int flags) final; + int32_t resolverStartNtoaMultipleRecords(SceNetId rid, const char* hostname, SceNetResolverInfo* info, int timeout, int retries, int flags) final; + int32_t resolverGetError(SceNetId rid, int* result) final; + int32_t resolverDestroy(SceNetId rid) final; + int32_t resolverAbort(SceNetId rid, int flags) final; + + /* Epoll sub-facility */ + SceNetId epollCreate(const char* name, int flags) final; + int32_t epollControl(SceNetId eid, int op, SceNetId id, SceNetEpollEvent* event) final; + int32_t epollWait(SceNetId eid, SceNetEpollEvent* events, int maxevents, int timeout) final; + int32_t epollDestroy(SceNetId eid) final; + int32_t epollAbort(SceNetId eid, int flags) final; + + /* Socket sub-facility */ + SceNetId socketCreate(const char* name, int family, int type, int protocol) final; + SceNetId socketAccept(SceNetId s, SceNetSockaddr* addr, SceNetSocklen_t* addrlen) final; + int socketBind(SceNetId s, const SceNetSockaddr* addr, SceNetSocklen_t addrlen) final; + int socketConnect(SceNetId s, const SceNetSockaddr* name, SceNetSocklen_t namelen) final; + int socketGetpeername(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) final; + int socketGetsockname(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) final; + int socketGetsockopt(SceNetId s, int level, int optname, void* optval, SceNetSocklen_t* optlen) final; + int socketListen(SceNetId s, int backlog) final; + int socketRecv(SceNetId s, void* buf, size_t len, int flags) final; + int socketRecvfrom(SceNetId s, void* buf, size_t len, int flags, SceNetSockaddr* from, SceNetSocklen_t* fromlen) final; + int socketRecvmsg(SceNetId s, SceNetMsghdr* msg, int flags) final; + int socketSend(SceNetId s, const void* msg, size_t len, int flags) final; + int socketSendto(SceNetId s, const void* msg, size_t len, int flags, const SceNetSockaddr* to, SceNetSocklen_t tolen) final; + int socketSendmsg(SceNetId s, const SceNetMsghdr* msg, int flags) final; + int socketSetsockopt(SceNetId s, int level, int optname, const void* optval, SceNetSocklen_t optlen) final; + int socketShutdown(SceNetId s, int how) final; + int socketClose(SceNetId s) final; + int socketAbort(SceNetId s, int flags) final; + + /* HTTP facility */ + + /* HTTP1 facility */ + int httpInit(int libnetMemId, int libsslCtxId, size_t poolSize) final; + int httpTerm(int libhttpCtxId) final; + int httpGetMemoryPoolStats(int libhttpCtxId, SceHttpMemoryPoolStats* currentStat) final; + int httpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer, int isAutoProxyConf) final; + int httpDeleteTemplate(int tmplId) final; + int httpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive) final; + int httpCreateConnectionWithURL(int tmplId, const char* url, int isEnableKeepalive) final; + int httpDeleteConnection(int connId) final; + int httpCreateRequest(int connId, int method, const char* path, uint64_t contentLength) final; + int httpCreateRequest2(int connId, const char* method, const char* path, uint64_t contentLength) final; + int httpCreateRequestWithURL(int connId, int method, const char* url, uint64_t contentLength) final; + int httpCreateRequestWithURL2(int connId, const char* method, const char* url, uint64_t contentLength) final; + int httpDeleteRequest(int reqId) final; + int httpSetRequestContentLength(int id, uint64_t contentLength) final; + int httpSetChunkedTransferEnabled(int id, int isEnable) final; + int httpSetInflateGZIPEnabled(int id, int isEnable) final; + int httpSendRequest(int reqId, const void* postData, size_t size) final; + int httpAbortRequest(int reqId) final; + int httpGetResponseContentLength(int reqId, int* result, uint64_t* contentLength) final; + int httpGetStatusCode(int reqId, int* statusCode) final; + int httpGetAllResponseHeaders(int reqId, char** header, size_t* headerSize) final; + int httpReadData(int reqId, void* data, size_t size) final; + int httpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode) final; + int httpRemoveRequestHeader(int id, const char* name) final; + int httpParseResponseHeader(const char* header, size_t headerLen, const char* fieldStr, const char** fieldValue, size_t* valueLen) final; + int httpParseStatusLine(const char* statusLine, size_t lineLen, int* httpMajorVer, int* httpMinorVer, int* responseCode, const char** reasonPhrase, + size_t* phraseLen) final; + int httpSetResponseHeaderMaxSize(int id, size_t headerSize) final; + int httpSetAuthInfoCallback(int id, SceHttpAuthInfoCallback cbfunc, void* userArg) final; + int httpSetAuthEnabled(int id, int isEnable) final; + int httpGetAuthEnabled(int id, int* isEnable) final; + int httpAuthCacheFlush(int libhttpCtxId) final; + int httpSetRedirectCallback(int id, SceHttpRedirectCallback cbfunc, void* userArg) final; + int httpSetAutoRedirect(int id, int isEnable) final; + int httpGetAutoRedirect(int id, int* isEnable) final; + int httpRedirectCacheFlush(int libhttpCtxId) final; + int httpSetResolveTimeOut(int id, uint32_t usec) final; + int httpSetResolveRetry(int id, int retry) final; + int httpSetConnectTimeOut(int id, uint32_t usec) final; + int httpSetSendTimeOut(int id, uint32_t usec) final; + int httpSetRecvTimeOut(int id, uint32_t usec) final; + int httpSetRequestStatusCallback(int id, SceHttpRequestStatusCallback cbfunc, void* userArg) final; + int httpGetLastErrno(int reqId, int* errNum) final; + int httpSetNonblock(int id, int isEnable) final; + int httpGetNonblock(int id, int* isEnable) final; + int httpTrySetNonblock(int id, int isEnable) final; + int httpTryGetNonblock(int id, int* isEnable) final; + int httpCreateEpoll(int libhttpCtxId, SceHttpEpollHandle* eh) final; + int httpSetEpoll(int id, SceHttpEpollHandle eh, void* userArg) final; + int httpUnsetEpoll(int id) final; + int httpGetEpoll(int id, SceHttpEpollHandle* eh, void** userArg) final; + int httpDestroyEpoll(int libhttpCtxId, SceHttpEpollHandle eh) final; + int httpWaitRequest(SceHttpEpollHandle eh, SceHttpNBEvent* nbev, int maxevents, int timeout) final; + int httpAbortWaitRequest(SceHttpEpollHandle eh) final; + + // HTTPS + int httpsLoadCert(int libhttpCtxId, int caCertNum, const SceSslData** caList, const SceSslData* cert, const SceSslData* privKey) final; + int httpsUnloadCert(int libhttpCtxId) final; + int httpsEnableOption(int id, uint32_t sslFlags) final; + int httpsDisableOption(int id, uint32_t sslFlags) final; + int httpsGetSslError(int id, int* errNum, uint32_t* detail) final; + int httpsSetSslCallback(int id, SceHttpsCallback cbfunc, void* userArg) final; + int httpsSetSslVersion(int id, SceSslVersion version) final; +}; diff --git a/core/networking/states/online/epoll.cpp b/core/networking/states/online/epoll.cpp new file mode 100644 index 00000000..8e049538 --- /dev/null +++ b/core/networking/states/online/epoll.cpp @@ -0,0 +1,97 @@ +#include "../online.h" +#include "logging.h" + +#include +#include +#include + +LOG_DEFINE_MODULE(OnlineNetCore_epoll); + +namespace { +constexpr int SCE_NET_EPOLLIN = 0x00000001; +constexpr int SCE_NET_EPOLLOUT = 0x00000002; +constexpr int SCE_NET_EPOLLERR = 0x00000008; +constexpr int SCE_NET_EPOLLHUP = 0x00000010; +constexpr int SCE_NET_EPOLLDESCID = 0x00010000; + +constexpr int SCE_NET_EPOLL_CTL_ADD = 1; +constexpr int SCE_NET_EPOLL_CTL_MOD = 2; +constexpr int SCE_NET_EPOLL_CTL_DEL = 3; + +constexpr int SCE_NET_EPOLL_ABORT_FLAG_PRESERVATION = 0x00000001; + +#define EPOLLDESCID (1u << 14) + +static inline uint32_t sce_map_epoll_events(uint32_t events) { + LOG_USE_MODULE(OnlineNetCore_epoll); + uint32_t _events = 0; + if (events & SCE_NET_EPOLLIN) _events |= EPOLLIN; + if (events & SCE_NET_EPOLLOUT) _events |= EPOLLOUT; + if (events & SCE_NET_EPOLLERR) _events |= EPOLLERR; + if (events & SCE_NET_EPOLLHUP) _events |= EPOLLHUP; + if (events & SCE_NET_EPOLLDESCID) _events |= EPOLLDESCID; // Not an actual event, todo + return _events; +} + +// Probably the safest way, since sizeof(SceNetId) < sizeof(HANDLE) +// We cannot guarantee that this HANDLE value will not exceed int32_t +static std::unordered_map map; + +static HANDLE sce_map_id_get(SceNetId id) { + if (map.find(id) != map.end()) return map[id]; + return INVALID_HANDLE_VALUE; +} + +static HANDLE sce_map_id_del(SceNetId id) { + auto it = map.find(id); + if (it != map.end()) { + auto value = map[id]; + map.erase(it); + return value; + } + return INVALID_HANDLE_VALUE; +} +} // namespace + +SceNetId OnlineNet::epollCreate(const char* name, int flags) { + static SceNetId id = 0; + HANDLE ep; + + if ((ep = epoll_create1(flags)) != NULL && ep != INVALID_HANDLE_VALUE) { + map[++id] = ep; + return id; + } + + return INetworking::getLastError(); +} + +int OnlineNet::epollControl(SceNetId eid, int op, SceNetId id, SceNetEpollEvent* event) { + epoll_event ev { + .events = sce_map_epoll_events(event->events), + .data = {.ptr = event->data.ptr}, + }; + + if (ev.events & EPOLLDESCID) return Ok; // todo handle all events + if (epoll_ctl(sce_map_id_get(eid), op, id, &ev) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::epollWait(SceNetId eid, SceNetEpollEvent* events, int maxevents, int timeout) { + epoll_event ev { + .events = sce_map_epoll_events(events->events), + .data = {.ptr = events->data.ptr}, + }; + + if (ev.events & EPOLLDESCID) return Ok; // todo handle all events + if (epoll_wait(sce_map_id_get(eid), &ev, maxevents, timeout) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::epollDestroy(SceNetId eid) { + if (epoll_close(sce_map_id_del(eid)) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::epollAbort(SceNetId eid, int flags) { + return Ok; +} diff --git a/core/networking/states/online/http.cpp b/core/networking/states/online/http.cpp new file mode 100644 index 00000000..9943b3af --- /dev/null +++ b/core/networking/states/online/http.cpp @@ -0,0 +1,719 @@ +#include "../online.h" +#include "logging.h" + +#include +#include +#include + +LOG_DEFINE_MODULE(OnlineNetCore_http); + +using namespace boost::asio; + +namespace { +constexpr size_t ARRAY_LENGTH = 128; + +template +class HttpContainer { + T* m_data[size] = {nullptr}; + + public: + int lookupAvailableIdx(T* action) { + int i; + bool found = false; + for (i = 1; i < size; i++) { + if (!m_data[i]) { + m_data[i] = action; + + break; + } + } + if (found == false) { + delete action; + return 0; + } + return i; + } + + int testId(int id) { + if (id < 1 || id >= size || m_data[id] == nullptr) return Err::HTTP_BAD_ID; + + return Ok; + } + + void remove(int id) { + delete m_data[id]; + m_data[id] = nullptr; + } + + T* getId(int id) { return m_data[id]; } +}; + +static io_service svc; +static ip::tcp::resolver resolver(svc); + +struct HttpClient { + int libnetMemId; + int libsslCtxId; + size_t poolSize; + uint32_t connectTimeout; + + HttpClient(int libnetMemId, int libsslCtxId, size_t poolSize): libnetMemId(libnetMemId), libsslCtxId(libsslCtxId), poolSize(poolSize) {} +}; + +struct HttpTemplate { + HttpClient* parentClient; + const char* userAgent; + int httpVer; + int isAutoProxyConf; + + HttpTemplate(HttpClient* parentClient, const char* userAgent, int httpVer, int isAutoProxyConf) + : parentClient(parentClient), userAgent(userAgent), httpVer(httpVer), isAutoProxyConf(isAutoProxyConf) {} +}; + +struct HttpConnection { + HttpTemplate* parentTemplate; + const char* serverName; + const char* scheme; + uint16_t port; + int isEnableKeepalive; + + bool connected = false; + bool shouldFreeStrings; + + ip::tcp::resolver::query* query = nullptr; + ip::tcp::resolver::iterator endpoint_iterator; + ip::tcp::socket* socket = nullptr; + + HttpConnection(HttpTemplate* parentTemplate, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive, bool shouldFreeStrings) + : parentTemplate(parentTemplate), + serverName(serverName), + scheme(scheme), + port(port), + isEnableKeepalive(isEnableKeepalive), + shouldFreeStrings(shouldFreeStrings) {} +}; + +struct HttpResponse { + int statusCode; + uint32_t contentLength; + const char* body = nullptr; +}; + +struct HttpRequest { + HttpConnection* parentConnection; + int method; + const char* path; + uint64_t contentLength; + const void* postData = nullptr; + size_t size; + HttpResponse* lastResponse = nullptr; + + bool shouldFreePath; + + HttpRequest(HttpConnection* parentConnection, int method, const char* path, uint64_t contentLength, bool shouldFreePath) + : parentConnection(parentConnection), method(method), path(path), contentLength(contentLength), shouldFreePath(shouldFreePath) {} +}; + +struct HttpRequestParams { + HttpConnection* connection; + HttpRequest* request; + uint32_t connectTimeout; + const char* userAgent; + int httpVer; + int isAutoProxyConf; + const char* serverName; + const char* scheme; + uint16_t port; + int isEnableKeepalive; + int method; + const char* path; + uint64_t contentLength; + const void* postData; // will be assigned to nullptr + size_t size; + + HttpRequestParams(HttpRequest* from) { + connection = from->parentConnection; + request = from; + connectTimeout = from->parentConnection->parentTemplate->parentClient->connectTimeout; + userAgent = from->parentConnection->parentTemplate->userAgent; + httpVer = from->parentConnection->parentTemplate->httpVer; + isAutoProxyConf = from->parentConnection->parentTemplate->isAutoProxyConf; + serverName = from->parentConnection->serverName; + scheme = from->parentConnection->scheme; + port = from->parentConnection->port; + isEnableKeepalive = from->parentConnection->isEnableKeepalive; + method = from->method; + path = from->path; + contentLength = from->contentLength; + postData = from->postData; + size = from->size; + } +}; + +static HttpContainer g_client; +static HttpContainer g_template; +static HttpContainer g_connection; +static HttpContainer g_request; + +#define GET_LAST_RESPONSE \ + HttpRequest* request = g_request.getId(reqId); \ + HttpResponse* response = request->lastResponse; \ + if (response == nullptr) return Err::HTTP_SEND_REQUIRED; + +static void deleteResponse(HttpResponse* response) { + if (response->body != nullptr) { + delete response->body; + } + delete response; +} + +static int httpMethodStringToInt(const char* method) { + if (::_stricmp(method, "get") == 0) { + return SCE_HTTP_GET; + } else if (::_stricmp(method, "post") == 0) { + return SCE_HTTP_POST; + } + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"unsupported http method: %S", method); + + return -1; +} + +static int32_t performHttpRequest(HttpRequestParams* request, HttpResponse* response) { + LOG_USE_MODULE(OnlineNetCore_http); + + HttpConnection* connection = request->connection; + try { + if (!connection->connected) { + connection->query = new ip::tcp::resolver::query(request->serverName, std::to_string(request->port)); + connection->endpoint_iterator = resolver.resolve(*connection->query); + connection->socket = new ip::tcp::socket(svc); + boost::asio::connect(*connection->socket, connection->endpoint_iterator); + connection->connected = true; + + if (std::strcmp(connection->scheme, "https") == 0) { + LOG_TRACE(L"detected an attempt to connect via https, it's not supported yet"); + + return Err::HTTP_SSL_ERROR; + } + if (std::strcmp(connection->scheme, "http") != 0) { + LOG_TRACE(L"unknown scheme: %S://", connection->scheme); + + return Err::HTTP_BAD_SCHEME; + } + } + if (request->request->lastResponse != nullptr) { + deleteResponse(request->request->lastResponse); + } + boost::asio::streambuf buffer; + std::ostream bufferStream(&buffer); + + if (request->method == SCE_HTTP_GET) { + bufferStream << "GET "; + } else if (request->method == SCE_HTTP_POST) { + bufferStream << "POST "; + } else { + LOG_TRACE(L"unsupported request method code (%d), pretending we've failed to connect", request->method); + + return Err::HTTP_FAILURE; + } + bufferStream << request->path; + if (request->httpVer == 1) + bufferStream << "HTTP/1.0"; + else + bufferStream << "HTTP/1.1"; + bufferStream << "\r\n"; + if (request->httpVer != 1 /* means HTTP 1.1 */) { + bufferStream << "Host: " << request->serverName << "\r\n"; + bufferStream << "User-Agent: " << request->userAgent << "\r\n"; + bufferStream << "Accept: */*\r\n"; + bufferStream << "Connection: " << (request->isEnableKeepalive ? "keep-alive" : "close") << "\r\n"; + } + if (request->postData != nullptr) { + bufferStream << "Content-Length: " << request->contentLength << "\r\n\r\n"; + for (size_t i = 0; i < request->size; i++) { + bufferStream << ((char*)request->postData)[i]; + } + } + boost::asio::write(*connection->socket, buffer); + + boost::asio::streambuf responseBuffer; + boost::asio::read_until(*connection->socket, responseBuffer, "\r\n"); + std::istream responseBufferStream(&responseBuffer); + + std::string httpVersion; + int statusCode; + std::string statusDescription; + responseBufferStream >> httpVersion; + responseBufferStream >> statusCode; + std::getline(responseBufferStream, statusDescription); + + boost::asio::read_until(*connection->socket, responseBuffer, "\r\n\r\n"); + + bool foundContentLengthHeader = false; + uint32_t contentLength; + std::string header; + while (std::getline(responseBufferStream, header) && header != "\r") { + if (header.rfind("Content-Length: ", 0) == 0) { // todo: remove case-sensitive check + std::string contentLengthUnparsed = header.substr(16); + contentLength = std::stoi(contentLengthUnparsed); + foundContentLengthHeader = true; + + break; + } + } + if (!foundContentLengthHeader) { + LOG_TRACE(L"failed to find \"Content-Length\" header in the response"); + + return Err::HTTP_FAILURE; + } + bool tooLow; + if ((tooLow = contentLength < 1) || contentLength > 1610612736) { + LOG_TRACE(L"bad content length: %S", (tooLow ? "less than 1 byte" : "more than 1.5 GiB")); + + return Err::HTTP_FAILURE; + } + size_t currentIdx = 0; + char* body; + try { + body = new char[contentLength]; + } catch (std::bad_alloc&) { + LOG_TRACE(L"failed to allocate %d bytes", contentLength); + + return Err::HTTP_FAILURE; + } + response->statusCode = statusCode; + response->contentLength = contentLength; + response->body = body; + + boost::system::error_code error; + while (boost::asio::read(*connection->socket, responseBuffer, boost::asio::transfer_at_least(1), error)) { + std::streamsize available = responseBuffer.in_avail(); + char c; + for (std::streamsize i = 0; i < available; i++) { + responseBufferStream >> c; + body[currentIdx++] = c; + } + } + if (error != boost::asio::error::eof) throw boost::system::system_error(error); + + return Ok; + } catch (const boost::exception& e) { + LOG_TRACE(L"caught a boost exception while performing an http request"); + + return Err::HTTP_FAILURE; + } catch (const std::exception& e) { + LOG_TRACE(L"caught an std::exception while performing an http request"); + + return Err::HTTP_FAILURE; + } +} +} // namespace + +int OnlineNet::httpInit(int libnetMemId, int libsslCtxId, size_t poolSize) { + auto const handle = g_client.lookupAvailableIdx(new HttpClient(libnetMemId, libsslCtxId, poolSize)); + if (handle == 0) return Err::HTTP_OUT_OF_MEMORY; + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"new http client (id: %d, memId: %d, sslCtxId: %d, poolSize: %d)", handle, libnetMemId, libsslCtxId, poolSize); + + return handle; +} + +int OnlineNet::httpTerm(int libhttpCtxId) { + if (auto ret = g_client.testId(libhttpCtxId)) return ret; + + g_client.remove(libhttpCtxId); + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"http client removed (id: %d)", libhttpCtxId); + + return Ok; +} + +int OnlineNet::httpGetMemoryPoolStats(int libhttpCtxId, SceHttpMemoryPoolStats* currentStat) { + if (auto ret = g_client.testId(libhttpCtxId)) return ret; + + currentStat->currentInuseSize = 16384; // todo (?) + currentStat->maxInuseSize = 131072; + currentStat->poolSize = g_client.getId(libhttpCtxId)->poolSize; + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"memory pool stats were requested (client id: %d)", libhttpCtxId); + + return Ok; +} + +int OnlineNet::httpCreateTemplate(int libhttpCtxId, const char* userAgent, int httpVer, int isAutoProxyConf) { + if (auto ret = g_client.testId(libhttpCtxId)) return ret; + + HttpClient* client = g_client.getId(libhttpCtxId); + + auto const handle = g_template.lookupAvailableIdx(new HttpTemplate(client, userAgent, httpVer, isAutoProxyConf)); + if (handle == 0) return Err::HTTP_OUT_OF_MEMORY; + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"new http template (id: %d, client id: %d)", handle, libhttpCtxId); + + return handle; +} + +int OnlineNet::httpDeleteTemplate(int tmplId) { + if (auto ret = g_template.testId(tmplId)) return ret; + + g_template.remove(tmplId); + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"http template removed (id: %d)", tmplId); + + return Ok; +} + +static int sceHttpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive, bool shouldFreeStrings) { + if (auto ret = g_template.testId(tmplId)) return ret; + + HttpTemplate* httpTemplate = g_template.getId(tmplId); + auto const handle = g_connection.lookupAvailableIdx(new HttpConnection(httpTemplate, serverName, scheme, port, isEnableKeepalive, shouldFreeStrings)); + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"new http connection (id: %d, template id: %d)", handle, tmplId); + + return handle; +} + +int OnlineNet::httpCreateConnection(int tmplId, const char* serverName, const char* scheme, uint16_t port, int isEnableKeepalive) { + return sceHttpCreateConnection(tmplId, serverName, scheme, port, isEnableKeepalive, false); +} + +int OnlineNet::httpCreateConnectionWithURL(int tmplId, const char* url, int isEnableKeepalive) { + boost::urls::url link(url); + uint16_t port; + + bool isNotHttp; + if ((isNotHttp = std::strcmp(link.scheme().data(), "http") != 0) && std::strcmp(link.scheme().data(), "https") != 0) { + + return Err::HTTP_BAD_SCHEME; + } + if (link.has_port()) { + port = link.port_number(); + } else { + if (isNotHttp) + port = 443; + else + port = 80; + } + + int result = sceHttpCreateConnection(tmplId, link.host().data(), link.scheme().data(), port, isEnableKeepalive, true); + + return result; +} + +int OnlineNet::httpDeleteConnection(int connId) { + if (auto ret = g_connection.testId(connId)) return ret; + + HttpConnection* connection = g_connection.getId(connId); + if (connection->query != nullptr) { + delete connection->query; + } + if (connection->socket != nullptr) { + connection->socket->close(); + + delete connection->socket; + } + if (connection->shouldFreeStrings) { + delete connection->scheme; + delete connection->serverName; + } + g_connection.remove(connId); + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"http connection removed (id: %d)", connId); + + return Ok; +} + +static int sceHttpCreateRequest(int connId, int method, const char* path, uint64_t contentLength, bool shouldFreePath) { + if (auto ret = g_connection.testId(connId)) return ret; + + HttpConnection* httpConnection = g_connection.getId(connId); + + auto handle = g_request.lookupAvailableIdx(new HttpRequest(httpConnection, method, path, contentLength, shouldFreePath)); + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"new http request (id: %d, connection id: %d)", handle, connId); + + return Ok; +} + +int OnlineNet::httpCreateRequest(int connId, int method, const char* path, uint64_t contentLength) { + return sceHttpCreateRequest(connId, method, path, contentLength, false); +} + +int OnlineNet::httpCreateRequest2(int connId, const char* method, const char* path, uint64_t contentLength) { + return httpCreateRequest(connId, httpMethodStringToInt(method), path, contentLength); +} + +int OnlineNet::httpCreateRequestWithURL(int connId, int method, const char* url, uint64_t contentLength) { + boost::urls::url link(url); + if (link.has_password()) { + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"urls containing credentials are not supported"); + + return Err::HTTP_BAD_PARAM; + } + + int result = sceHttpCreateRequest(connId, method, link.path().data(), contentLength, true); + + return result; +} + +int OnlineNet::httpCreateRequestWithURL2(int connId, const char* method, const char* url, uint64_t contentLength) { + return httpCreateRequestWithURL(connId, httpMethodStringToInt(method), url, contentLength); +} + +int OnlineNet::httpDeleteRequest(int reqId) { + if (auto ret = g_request.testId(reqId)) return ret; + + HttpRequest* request = g_request.getId(reqId); + if (request->lastResponse != nullptr) { + deleteResponse(request->lastResponse); + } + if (request->shouldFreePath) { + delete request->path; + } + g_request.remove(reqId); + + LOG_USE_MODULE(OnlineNetCore_http); + LOG_TRACE(L"http request removed (id: %d)", reqId); + + return Ok; +} + +int OnlineNet::httpSetRequestContentLength(int id, uint64_t contentLength) { + int reqId = id; // (?) + if (auto ret = g_request.testId(reqId)) return ret; + + g_request.getId(reqId)->contentLength = contentLength; + + return Ok; +} + +int OnlineNet::httpSetChunkedTransferEnabled(int id, int isEnable) { + return Ok; +} + +int OnlineNet::httpSetInflateGZIPEnabled(int id, int isEnable) { + return Ok; +} + +int OnlineNet::httpSendRequest(int reqId, const void* postData, size_t size) { + if (auto ret = g_request.testId(reqId)) return ret; + + HttpRequest* request = g_request.getId(reqId); + request->postData = postData; + request->size = size; + HttpRequestParams* fullParams = new HttpRequestParams(request); + HttpResponse* response = new HttpResponse(); + int32_t result = performHttpRequest(fullParams, response); + delete fullParams; + if (result == Ok) { + request->lastResponse = response; + } else { + deleteResponse(response); + } + + return result; +} + +int OnlineNet::httpAbortRequest(int reqId) { + return Ok; +} + +int OnlineNet::httpGetResponseContentLength(int reqId, int* result, uint64_t* contentLength) { + if (auto ret = g_request.testId(reqId)) return ret; + + GET_LAST_RESPONSE; + *result = 0; // Content-Length is guaranteed to exist, otherwise performHttpRequest would have failed + *contentLength = response->contentLength; + + return Ok; +} + +int OnlineNet::httpGetStatusCode(int reqId, int* statusCode) { + if (auto ret = g_request.testId(reqId)) return ret; + + GET_LAST_RESPONSE; + *statusCode = response->statusCode; + + return Ok; +} + +int OnlineNet::httpGetAllResponseHeaders(int reqId, char** header, size_t* headerSize) { + *headerSize = 0; + + return Ok; +} + +int OnlineNet::httpReadData(int reqId, void* data, size_t size) { + if (auto ret = g_request.testId(reqId)) return ret; + + GET_LAST_RESPONSE; + size_t finalSize = min(size, response->contentLength); + memcpy(data, response->body, finalSize); + + return Ok; +} + +int OnlineNet::httpAddRequestHeader(int id, const char* name, const char* value, uint32_t mode) { + return Ok; +} + +int OnlineNet::httpRemoveRequestHeader(int id, const char* name) { + return Ok; +} + +int OnlineNet::httpParseResponseHeader(const char* header, size_t headerLen, const char* fieldStr, const char** fieldValue, size_t* valueLen) { + return Ok; +} + +int OnlineNet::httpParseStatusLine(const char* statusLine, size_t lineLen, int* httpMajorVer, int* httpMinorVer, int* responseCode, const char** reasonPhrase, + size_t* phraseLen) { + return Ok; +} + +int OnlineNet::httpSetResponseHeaderMaxSize(int id, size_t headerSize) { + return Ok; +} + +int OnlineNet::httpSetAuthInfoCallback(int id, SceHttpAuthInfoCallback cbfunc, void* userArg) { + return Ok; +} + +int OnlineNet::httpSetAuthEnabled(int id, int isEnable) { + return Ok; +} + +int OnlineNet::httpGetAuthEnabled(int id, int* isEnable) { + return Ok; +} + +int OnlineNet::httpAuthCacheFlush(int libhttpCtxId) { + return Ok; +} + +int OnlineNet::httpSetRedirectCallback(int id, SceHttpRedirectCallback cbfunc, void* userArg) { + return Ok; +} + +int OnlineNet::httpSetAutoRedirect(int id, int isEnable) { + return Ok; +} + +int OnlineNet::httpGetAutoRedirect(int id, int* isEnable) { + return Ok; +} + +int OnlineNet::httpRedirectCacheFlush(int libhttpCtxId) { + return Ok; +} + +int OnlineNet::httpSetResolveTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OnlineNet::httpSetResolveRetry(int id, int retry) { + return Ok; +} + +int OnlineNet::httpSetConnectTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OnlineNet::httpSetSendTimeOut(int id, uint32_t usec) { + return Err::SEND_TIMEOUT; +} + +int OnlineNet::httpSetRecvTimeOut(int id, uint32_t usec) { + return Ok; +} + +int OnlineNet::httpSetRequestStatusCallback(int id, SceHttpRequestStatusCallback cbfunc, void* userArg) { + return Ok; +} + +int OnlineNet::httpGetLastErrno(int reqId, int* errNum) { + return Ok; +} + +int OnlineNet::httpSetNonblock(int id, int isEnable) { + return Ok; +} + +int OnlineNet::httpGetNonblock(int id, int* isEnable) { + return Ok; +} + +int OnlineNet::httpTrySetNonblock(int id, int isEnable) { + return Ok; +} + +int OnlineNet::httpTryGetNonblock(int id, int* isEnable) { + return Ok; +} + +int OnlineNet::httpCreateEpoll(int libhttpCtxId, SceHttpEpollHandle* eh) { + return Ok; +} + +int OnlineNet::httpSetEpoll(int id, SceHttpEpollHandle eh, void* userArg) { + return Ok; +} + +int OnlineNet::httpUnsetEpoll(int id) { + return Ok; +} + +int OnlineNet::httpGetEpoll(int id, SceHttpEpollHandle* eh, void** userArg) { + return Ok; +} + +int OnlineNet::httpDestroyEpoll(int libhttpCtxId, SceHttpEpollHandle eh) { + return Ok; +} + +int OnlineNet::httpWaitRequest(SceHttpEpollHandle eh, SceHttpNBEvent* nbev, int maxevents, int timeout) { + return Ok; +} + +int OnlineNet::httpAbortWaitRequest(SceHttpEpollHandle eh) { + return Ok; +} + +// HTTPS +int OnlineNet::httpsLoadCert(int libhttpCtxId, int caCertNum, const SceSslData** caList, const SceSslData* cert, const SceSslData* privKey) { + return Ok; +} + +int OnlineNet::httpsUnloadCert(int libhttpCtxId) { + return Ok; +} + +int OnlineNet::httpsEnableOption(int id, uint32_t sslFlags) { + return Ok; +} + +int OnlineNet::httpsDisableOption(int id, uint32_t sslFlags) { + return Ok; +} + +int OnlineNet::httpsGetSslError(int id, int* errNum, uint32_t* detail) { + return Ok; +} + +int OnlineNet::httpsSetSslCallback(int id, SceHttpsCallback cbfunc, void* userArg) { + return Ok; +} + +int OnlineNet::httpsSetSslVersion(int id, SceSslVersion version) { + return Ok; +} diff --git a/core/networking/states/online/netctl.cpp b/core/networking/states/online/netctl.cpp new file mode 100644 index 00000000..4ac3b2f8 --- /dev/null +++ b/core/networking/states/online/netctl.cpp @@ -0,0 +1,196 @@ +#include "../online.h" +#include "config_emu.h" +#include "logging.h" + +#include + +LOG_DEFINE_MODULE(OnlineNetCore); + +int32_t OnlineNet::netCtlGetInfo(int32_t code, SceNetCtlInfo* info) { + LOG_USE_MODULE(OnlineNetCore); + PIP_ADAPTER_INFO pAda = nullptr; // Adapters head + PIP_ADAPTER_INFO pCurrAda = nullptr; // Used adapter + ULONG uAdaBufLen; + PIP_ADAPTER_ADDRESSES pAddr = nullptr; // Addresses head + PIP_ADAPTER_ADDRESSES pCurrAddr = nullptr; // Used adapter addr + ULONG uAddrBufLen; + + auto initAdapterInfo = [&pAda, &pCurrAda, &uAdaBufLen]() { + LOG_USE_MODULE(OnlineNetCore); + pAda = nullptr; + uAdaBufLen = 0; + + if (GetAdaptersInfo(nullptr, &uAdaBufLen) == ERROR_BUFFER_OVERFLOW) { // Obtaining buffer length + pAda = (PIP_ADAPTER_INFO)malloc(uAdaBufLen); + } else { + LOG_ERR(L"Failed to obtain size needed for GetAdaptersInfo buffer!"); + return Err::Net::ERROR_EFAULT; + } + + if (auto ret = GetAdaptersInfo(pAda, &uAdaBufLen)) { + free(pAda); + pAda = nullptr; + LOG_ERR(L"GetAdaptersInfo failed: %x", ret); + return Err::Net::ERROR_EFAULT; + } + + auto [lock, jData] = accessConfig()->accessModule(ConfigModFlag::GENERAL); + std::string macAddr; + + if (!getJsonParam(jData, "netMAC", macAddr)) { + macAddr = "00:00:00:00:00:00"; + } + + pCurrAda = pAda; + if (macAddr == "00:00:00:00:00:00") { + while (pCurrAda != nullptr) { // Find the first adapter with non-zero ip address + if (strcmp(pCurrAda->IpAddressList.IpAddress.String, "0.0.0.0") != 0) break; + pCurrAda = pCurrAda->Next; + } + } else { + uint8_t exMAC[6] = {0}; // Expected MAC address + if (sscanf_s(macAddr.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &exMAC[0], &exMAC[1], &exMAC[2], &exMAC[3], &exMAC[4], &exMAC[5]) == 6) { + while (pCurrAda != nullptr) { // Find the adapter with specified MAC address + if (pCurrAda->AddressLength == sizeof(exMAC) && memcmp(pCurrAda->Address, exMAC, sizeof(exMAC)) == 0) break; + pCurrAda = pCurrAda->Next; + } + } else { + LOG_ERR(L"initAdapterInfo: failed to parse netMAC value from config!"); + } + } + + if (pCurrAda != nullptr) return Ok; + LOG_ERR(L"initAdapterInfo: failed to find suitable network adapter!"); + free(pAda); + pAda = pCurrAda = nullptr; + return Err::Net::ERROR_EFAULT; + }; + + auto initAdapterAddr = [&pAddr, &pCurrAddr, &uAddrBufLen, &pAda, &pCurrAda, initAdapterInfo]() { + LOG_USE_MODULE(OnlineNetCore); + pAddr = nullptr; + uAddrBufLen = 0; + + if (GetAdaptersAddresses(AF_INET, 0, nullptr, nullptr, &uAddrBufLen) == ERROR_BUFFER_OVERFLOW) { // Obtaining buffer length + pAddr = (PIP_ADAPTER_ADDRESSES)malloc(uAddrBufLen); + } else { + LOG_ERR(L"Failed to obtain size needed for GetAdaptersAddresses buffer!"); + return Err::Net::ERROR_EFAULT; + } + + if (auto ret = GetAdaptersAddresses(AF_INET, 0, nullptr, pAddr, &uAddrBufLen)) { + free(pAddr); + pAddr = nullptr; + LOG_ERR(L"GetAdaptersAddresses failed: %d (%d)", ret, uAddrBufLen); + return Err::Net::ERROR_EFAULT; + } + + if (auto ret = initAdapterInfo()) { // Trying to find suitable adapter first + free(pAddr); + pAddr = nullptr; + return ret; + } + + pCurrAddr = pAddr; + while (pCurrAddr != nullptr) { // Adapter found, okay. Now we should match this adapter with its address + if (pCurrAda->AddressLength == pCurrAddr->PhysicalAddressLength && memcmp(pCurrAddr->PhysicalAddress, pCurrAda->Address, pCurrAda->AddressLength) == 0) + break; + pCurrAddr = pAddr->Next; + } + + if (pCurrAddr != nullptr) return Ok; + LOG_ERR(L"initAdapterAddr: failed to find suitable network address!"); + free(pAddr); + free(pAda); + pAddr = pCurrAddr = nullptr; + pAda = pCurrAda = nullptr; + return Ok; + }; + + switch (code) { + case 1: info->device = 0; break; + case 2: { + if (auto ret = initAdapterInfo()) return ret; + memcpy(info->ether_addr.data, pCurrAda->Address, pCurrAda->AddressLength); + } break; + case 3: { + if (auto ret = initAdapterAddr()) return ret; + info->mtu = (uint32_t)pCurrAddr->Mtu; + } break; + case 4: info->link = 1; break; + case 5: + case 6: + case 7: // Big and scary fallthrough + case 8: + case 9: + case 10: return Err::Net::ERROR_NOT_AVAIL; // All these above for WiFi + case 11: info->ip_config = 0; break; + case 12: { + DWORD size = sizeof(info->dhcp_hostname); + if (GetComputerNameA(info->dhcp_hostname, &size)) break; // todo: shouldn't be DNS suffix there? + info->dhcp_hostname[0] = '\0'; + } break; + case 13: { + auto const count = std::string("ps4_pppoe").copy(info->pppoe_auth_name, 128); // Huh? + + info->pppoe_auth_name[count] = '\0'; + } break; + case 14: { + if (auto ret = initAdapterInfo()) return ret; + auto const count = std::string(pCurrAda->IpAddressList.IpAddress.String).copy(info->ip_address, 16); + + info->ip_address[count] = '\0'; + } break; + case 15: { + if (auto ret = initAdapterInfo()) return ret; + auto const count = std::string(pCurrAda->IpAddressList.IpMask.String).copy(info->netmask, 16); + + info->netmask[count] = '\0'; + } break; + case 16: { + if (auto ret = initAdapterInfo()) return ret; + auto const count = std::string(pCurrAda->GatewayList.IpAddress.String).copy(info->default_route, 16); + + info->default_route[count] = '\0'; + } break; + case 17: { + if (auto ret = initAdapterAddr()) return ret; + auto dns = pCurrAddr->FirstDnsServerAddress; + if (dns == nullptr) { + info->primary_dns[0] = '\0'; + break; + } + auto addr = &pCurrAddr->FirstDnsServerAddress->Address; + DWORD pdnssz = sizeof(info->primary_dns); + if (WSAAddressToStringA(addr->lpSockaddr, addr->iSockaddrLength, nullptr, info->primary_dns, &pdnssz) == SOCKET_ERROR) + info->primary_dns[0] = '\0'; // todo return error? + } break; + case 18: { + if (auto ret = initAdapterAddr()) return ret; + auto dns = pCurrAddr->FirstDnsServerAddress; + if (dns == nullptr) { + info->secondary_dns[0] = '\0'; + break; + } + if (dns->Next != nullptr) dns = dns->Next; // Use the secondary DNS if present + auto addr = &dns->Address; + DWORD pdnssz = sizeof(info->secondary_dns); + if (WSAAddressToStringA(addr->lpSockaddr, addr->iSockaddrLength, nullptr, info->secondary_dns, &pdnssz) == SOCKET_ERROR) + info->secondary_dns[0] = '\0'; // todo return error? + } break; + case 19: info->http_proxy_config = 0; break; + case 20: *info->http_proxy_server = '\0'; break; + case 21: info->http_proxy_port = 0; break; + default: LOG_CRIT(L"unknown code: %d", code); + } + + if (pAda != nullptr) free(pAda); + if (pAddr != nullptr) free(pAddr); + + return Ok; +} + +int32_t OnlineNet::netCtlGetState(int32_t* state) { + *state = 3; // IP address obtained + return Ok; +} diff --git a/core/networking/states/online/resolver.cpp b/core/networking/states/online/resolver.cpp new file mode 100644 index 00000000..35624f17 --- /dev/null +++ b/core/networking/states/online/resolver.cpp @@ -0,0 +1,231 @@ +#include "../online.h" +#include "config_emu.h" +#include "logging.h" + +#include +#include +#include + +LOG_DEFINE_MODULE(OnlineNetCore_resolv); + +using namespace boost::asio; + +namespace { +static io_service svc; + +struct Resolver { + char name[32]; + ip::tcp::resolver res; + bool interrupted; + int internError; + + Resolver(): res(svc), interrupted(false), name() {} +}; + +static Resolver* g_resolvers[128] = {nullptr}; + +class PreMap { + std::unordered_map m_mapAddrs; + + public: + PreMap() { + LOG_USE_MODULE(OnlineNetCore_resolv); + auto [lock, jData] = accessConfig()->accessModule(ConfigModFlag::RESOLVE); + + if ((*jData).is_object()) { + for (auto [key, value]: (*jData).items()) { + try { + SceNetInAddr_t addr; + if (value.is_number_integer()) { + value.get_to(addr); + } else { + std::string saddr; + value.get_to(saddr); + addr = ip::address_v4::from_string(saddr).to_ulong(); + } + + m_mapAddrs.insert(std::pair(key, addr)); + } catch (json::exception& ex) { + LOG_ERR(L"Invalid resolve.json field: %S", key.c_str()); + } + } + return; + } + + LOG_ERR(L"Something wrong with your resolve.json!"); + } + + SceNetInAddr_t search(const std::string& hostname) { + auto it = m_mapAddrs.find(hostname); + if (it != m_mapAddrs.end()) { + return it->second; + } + + return 0x00000000; + } + + std::string_view const reverse_search(const SceNetInAddr_t addr) { + auto it = std::find_if(m_mapAddrs.begin(), m_mapAddrs.end(), [&addr](auto&& p) { return p.second == addr; }); + if (it == m_mapAddrs.end()) return ""; + return it->first; + } +}; + +PreMap& getPreMap() { + static PreMap imp; + return imp; +} + +static inline int32_t testResolver(SceNetId rid) { + if (rid < 0 || rid > 127 || g_resolvers[rid] == nullptr) return Err::Net::ERROR_EINVAL; + return Ok; +} +} // namespace + +SceNetId OnlineNet::resolverCreate(const char* name, int memid, int flags) { + LOG_USE_MODULE(OnlineNetCore_resolv); + + if (flags != 0) { + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_EINVAL; + return -1; + } + + for (int i = 0; i < 128; i++) { + if (g_resolvers[i] == nullptr) { + auto resolv = g_resolvers[i] = new Resolver(); + LOG_TRACE(L"new resolver: %S,%d (memid: %d, flags: %d)", name, i, memid, flags); + + if (auto namelen = ::strlen(name)) { + if (namelen > sizeof(Resolver::name) - 1) { + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_ENAMETOOLONG; + return -1; + } + ::strncpy_s(resolv->name, name, namelen); + } + return i; + } + } + + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_EBADF; + return -1; +} + +int32_t OnlineNet::resolverStartNtoa(SceNetId rid, const char* hostname, SceNetInAddr_t* addr, int timeout, int retries, int flags) { + LOG_USE_MODULE(OnlineNetCore_resolv); + if (auto ret = testResolver(rid)) return ret; + + auto resolver = g_resolvers[rid]; + resolver->internError = 0; + + { + std::string _hostname(hostname); + if (auto _raddr = getPreMap().search(_hostname)) { + *addr = _raddr; + return Ok; + } + } + + ip::tcp::resolver::query query(ip::tcp::v4(), hostname, "0"); + for (int cretr = 0; cretr < retries; ++cretr) { + if (resolver->interrupted) { + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_EINTR; + return Err::Net::ERROR_EFAULT; + } + try { + auto it = resolver->res.resolve(query); + *addr = (*it).endpoint().address().to_v4().to_uint(); + return Ok; + } catch (boost::system::system_error& ex) { + LOG_ERR(L"%S: failed to resolve %S (%d/%d): %S", __FUNCTION__, hostname, cretr, retries, ex.what()); + } + } + + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_RESOLVER_ETIMEDOUT; + return getErr(ErrCode::_ETIMEDOUT); +} + +int32_t OnlineNet::resolverStartAton(SceNetId rid, const SceNetInAddr_t* addr, char* hostname, int len, int timeout, int retry, int flags) { + LOG_USE_MODULE(OnlineNetCore_resolv); + if (auto ret = testResolver(rid)) return ret; + LOG_ERR(L"todo %S", __FUNCTION__); + + auto resolver = g_resolvers[rid]; + resolver->internError = 0; + + { + auto _hn = getPreMap().reverse_search(*addr); + if (auto _hnlen = _hn.length()) { + if (len <= _hnlen) return Err::Net::ERROR_EINVAL; + _hn.copy(hostname, _hnlen + 1); + return Ok; + } + } + + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_RESOLVER_ETIMEDOUT; + return getErr(ErrCode::_ETIMEDOUT); +} + +int32_t OnlineNet::resolverStartNtoaMultipleRecords(SceNetId rid, const char* hostname, SceNetResolverInfo* info, int timeout, int retries, int flags) { + LOG_USE_MODULE(OnlineNetCore_resolv); + if (auto ret = testResolver(rid)) return ret; + + auto resolver = g_resolvers[rid]; + resolver->internError = 0; + + { + std::string _hostname(hostname); + if (auto _raddr = getPreMap().search(_hostname)) { + info->addrs[0].af = SCE_NET_AF_INET; + info->addrs[0].un.addr = _raddr; + info->records = 1; + return Ok; + } + } + + ip::tcp::resolver::query query(ip::tcp::v4(), hostname, "0"); + info->records = 0; + for (int cretr = 0; cretr < retries; ++cretr) { + // todo: should set sce_net_errno + if (resolver->interrupted) { + resolver->internError = NetErrNo::SCE_NET_EINTR; + return Err::Net::ERROR_EFAULT; + } + try { + for (auto addr: resolver->res.resolve(query)) { + auto& iaddr = info->addrs[info->records++]; + iaddr.af = SCE_NET_AF_INET; + iaddr.un.addr = addr.endpoint().address().to_v4().to_uint(); + if (info->records == SCE_NET_RESOLVER_MULTIPLE_RECORDS_MAX) break; + } + info->records -= 1; // We should decrease value by 1 to get the actual records count + info->dns4records = info->records; + return Ok; + } catch (boost::system::system_error& ex) { + LOG_ERR(L"%S: failed to resolve %S (%d/%d): %S", __FUNCTION__, hostname, cretr, retries, ex.what()); + } + } + + // *sceNetErrnoLoc() = NetErrNo::SCE_NET_RESOLVER_ETIMEDOUT; + return getErr(ErrCode::_ETIMEDOUT); +} + +int32_t OnlineNet::resolverGetError(SceNetId rid, int* result) { + LOG_USE_MODULE(OnlineNetCore_resolv); + if (auto ret = testResolver(rid)) return ret; + LOG_ERR(L"todo %S", __FUNCTION__); + *result = g_resolvers[rid]->internError; + return Ok; +} + +int32_t OnlineNet::resolverDestroy(SceNetId rid) { + if (auto ret = testResolver(rid)) return ret; + delete g_resolvers[rid]; + g_resolvers[rid] = nullptr; + return Ok; +} + +int32_t OnlineNet::resolverAbort(SceNetId rid, int flags) { + if (auto ret = testResolver(rid)) return ret; + g_resolvers[rid]->interrupted = true; + return Ok; +} diff --git a/core/networking/states/online/socket.cpp b/core/networking/states/online/socket.cpp new file mode 100644 index 00000000..27fb1f14 --- /dev/null +++ b/core/networking/states/online/socket.cpp @@ -0,0 +1,113 @@ +#include "../online.h" + +SceNetId OnlineNet::socketCreate(const char* name, int family, int type, int protocol) { + if (family != SCE_NET_AF_INET) { + *INetworking::getErrnoPtr() = NetErrNo::SCE_NET_EPROTONOSUPPORT; + return Err::Net::ERROR_EINVAL; + } + + switch (type) { + case SCE_NET_SOCK_STREAM: + case SCE_NET_SOCK_DGRAM: + case SCE_NET_SOCK_RAW: return socket(AF_INET, type, protocol); + + default: { + *INetworking::getErrnoPtr() = NetErrNo::SCE_NET_EPROTONOSUPPORT; + return Err::Net::ERROR_EPROTOTYPE; + } + } +} + +SceNetId OnlineNet::socketAccept(SceNetId s, SceNetSockaddr* addr, SceNetSocklen_t* addrlen) { + int _addrlen = addrlen ? (int)*addrlen : sizeof(SceNetSockaddr); + SOCKET _ret = accept(s, (sockaddr*)addr, &_addrlen); + if (_ret == INVALID_SOCKET) return INetworking::getLastError(); + *addrlen = (SceNetSocklen_t)_addrlen; + return (SceNetId)_ret; +} + +int OnlineNet::socketBind(SceNetId s, const SceNetSockaddr* addr, SceNetSocklen_t addrlen) { + if (bind(s, (const sockaddr*)addr, addrlen) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketConnect(SceNetId s, const SceNetSockaddr* name, SceNetSocklen_t namelen) { + if (connect(s, (const sockaddr*)name, namelen) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketGetpeername(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) { + if (getpeername(s, (sockaddr*)name, (int*)namelen) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketGetsockname(SceNetId s, SceNetSockaddr* name, SceNetSocklen_t* namelen) { + if (getsockname(s, (sockaddr*)name, (int*)namelen) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketGetsockopt(SceNetId s, int level, int optname, void* optval, SceNetSocklen_t* optlen) { + int _optlen = optlen ? (int)*optlen : sizeof(SceNetSockaddr); + int _ret = getsockopt(s, level, optname, (char*)optval, &_optlen); + if (_ret == SOCKET_ERROR) return INetworking::getLastError(); + *optlen = (SceNetSocklen_t)_optlen; + return _ret; +} + +int OnlineNet::socketListen(SceNetId s, int backlog) { + if (listen(s, backlog) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketRecv(SceNetId s, void* buf, size_t len, int flags) { + int _ret = recv(s, (char*)buf, (int)len, flags); + if (_ret == SOCKET_ERROR) return INetworking::getLastError(); + return _ret; +} + +int OnlineNet::socketRecvfrom(SceNetId s, void* buf, size_t len, int flags, SceNetSockaddr* from, SceNetSocklen_t* fromlen) { + int _fromlen = fromlen ? (int)*fromlen : sizeof(SceNetSockaddr); + int _ret = recvfrom(s, (char*)buf, (int)len, flags, (sockaddr*)from, &_fromlen); + if (_ret == SOCKET_ERROR) return INetworking::getLastError(); + *fromlen = (SceNetSocklen_t)_fromlen; + return _ret; +} + +int OnlineNet::socketRecvmsg(SceNetId s, SceNetMsghdr* msg, int flags) { + return Ok; +} + +int OnlineNet::socketSend(SceNetId s, const void* msg, size_t len, int flags) { + int _ret; + if ((_ret = send(s, (const char*)msg, (int)len, flags)) == SOCKET_ERROR) return INetworking::getLastError(); + return _ret; +} + +int OnlineNet::socketSendto(SceNetId s, const void* msg, size_t len, int flags, const SceNetSockaddr* to, SceNetSocklen_t tolen) { + int _ret; + if ((_ret = sendto(s, (const char*)msg, (int)len, flags, (const sockaddr*)to, (int)tolen)) == SOCKET_ERROR) return INetworking::getLastError(); + return _ret; +} + +int OnlineNet::socketSendmsg(SceNetId s, const SceNetMsghdr* msg, int flags) { + return Ok; +} + +int OnlineNet::socketSetsockopt(SceNetId s, int level, int optname, const void* optval, SceNetSocklen_t optlen) { + if (setsockopt(s, level, optname, (char*)optval, (int)optlen) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketShutdown(SceNetId s, int how) { + if (shutdown(s, how) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketClose(SceNetId s) { + if (closesocket(s) == SOCKET_ERROR) return INetworking::getLastError(); + return Ok; +} + +int OnlineNet::socketAbort(SceNetId s, int flags) { + return Ok; +} diff --git a/core/readme.md b/core/readme.md new file mode 100644 index 00000000..f645e3d9 --- /dev/null +++ b/core/readme.md @@ -0,0 +1,10 @@ +# Overview + +
+ +![](../out/docs/uml/modules/coreDeps.svg) +
+ +All communication goes through the core library. The callbacks to the emulator are set during startup, before loading and setting up the target library. + +Modules should only communicate unidirectional with the core library. Everything that can't be done locally inside modules, goes here. \ No newline at end of file diff --git a/core/runtime/CMakeLists.txt b/core/runtime/CMakeLists.txt new file mode 100644 index 00000000..43e2fcfd --- /dev/null +++ b/core/runtime/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(runtime OBJECT + util/exceptionHandler.cpp + util/moduleLoader.cpp + util/virtualmemory.cpp + + formats/elf64.cpp + exports/intern.cpp + + runtimeLinker.cpp +) + +add_dependencies(runtime third_party psOff_utility) + +file(COPY runtimeLinker.h runtimeExport.h program.h procParam.h DESTINATION ${DST_INCLUDE_DIR}/runtime) +file(COPY exports/intern.h exports/macro.h DESTINATION ${DST_INCLUDE_DIR}/runtime/exports) +file(COPY formats/ISymbols.h formats/IFormat.h DESTINATION ${DST_INCLUDE_DIR}/runtime/formats) \ No newline at end of file diff --git a/core/runtime/exports/intern.cpp b/core/runtime/exports/intern.cpp new file mode 100644 index 00000000..ab38642d --- /dev/null +++ b/core/runtime/exports/intern.cpp @@ -0,0 +1,59 @@ +#define __APICALL_EXTERN +#include "intern.h" +#undef __APICALL_EXTERN + +#include "../runtimeLinker.h" +#include "macro.h" +#include "utility/utility.h" + +namespace intern { + +SYSV_ABI void module_start(size_t argc, const void* argp, int* pRes) { + // todo ? +} + +void initIntercepts(); + +void init() { + LIB_DEFINE_START("intern", 0, "intern", 0, 0) + LIB_FUNC("module_start", module_start), LIB_DEFINE_END(); + + initIntercepts(); +} + +/* +void SYSV_ABI logfunc(const char* str, void* args) { + char sanitize[1024] = "gamelogger| "; + for (int i = 12; i < 1024; ++i) { + if (*str >= ' ' && *str <= '~') + sanitize[i] = *str++; + else { + sanitize[i] = '\n'; + sanitize[i + 1] = '\0'; + break; + } + } + vprintf(sanitize, (va_list)&args); +} +*/ + +void post() { + /* + // NieR: Automata v01.06 internal game logger interception example: + auto mp = accessRuntimeLinker().accessMainProg(); + accessRuntimeLinker().interceptInternal(mp, 0xbe09e0, (uint64_t)&logfunc); + */ +} + +void initIntercepts() { + /*Usage + // functions has to be of SYSV_ABI! + accessRuntimeLinker().interceptAdd((uintptr_t)int_Malloc, "Y7aJ1uydPMo", "libc", "libc") + + // Calling original + auto const origFunction = accessRuntimeLinker().interceptGetAddr((uintptr_t)int_Malloc); + typedef SYSV_ABI void* (*fcnPtr)(void*, size_t); + void* ret = ((fcnPtr)origFunction)(ptr, size); + */ +} +} // namespace intern diff --git a/core/runtime/exports/intern.h b/core/runtime/exports/intern.h new file mode 100644 index 00000000..47e0eef6 --- /dev/null +++ b/core/runtime/exports/intern.h @@ -0,0 +1,16 @@ +#pragma once + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif + +namespace intern { +__APICALL void init(); +__APICALL void post(); +} // namespace intern + +#undef __APICALL diff --git a/core/runtime/exports/macro.h b/core/runtime/exports/macro.h new file mode 100644 index 00000000..656e6d0a --- /dev/null +++ b/core/runtime/exports/macro.h @@ -0,0 +1,26 @@ +#pragma once + +#define LIB_DEFINE_START(lib, ver, mod, verMajor, verMinor) \ + { \ + auto libInfo = std::make_unique(lib, ver, mod, verMajor, verMinor); \ + libInfo->symbolsMap = { + +#define LIB_FUNC(sym, func) \ + { \ + sym, { \ + sym, #func, reinterpret_cast(func), Symbols::SymbolType::Func \ + } \ + } + +#define LIB_OBJECT(sym, obj) \ + { \ + sym, { \ + sym, #obj, reinterpret_cast(obj), Symbols::SymbolType::Object \ + } \ + } + +#define LIB_DEFINE_END() \ + } \ + ; \ + accessRuntimeLinker().addExport(std::move(libInfo)); \ + } \ No newline at end of file diff --git a/core/runtime/formats/IFormat.h b/core/runtime/formats/IFormat.h new file mode 100644 index 00000000..7c493a22 --- /dev/null +++ b/core/runtime/formats/IFormat.h @@ -0,0 +1,68 @@ +#pragma once +#include "../program.h" +#include "ISymbols.h" +#include "utility/utility.h" + +#include +#include +#include +#include + +class IRuntimeLinker; + +struct LibraryId { + bool operator==(const LibraryId& other) const { return version == other.version && name == other.name; } + + std::string id; + int version; + std::string_view name; +}; + +class IFormat: public Symbols::IResolve { + CLASS_NO_COPY(IFormat); + + protected: + uint64_t m_baseVaddr; // Set with load2Mem + size_t m_fileSize; + + std::filesystem::path const m_path; /// Fullpath + std::filesystem::path const m_filename; /// Just the filename + + util::InterfaceState m_intState = util::InterfaceState::NotInit; + + IFormat(std::filesystem::path&& path): m_path(std::move(path)), m_filename(m_path.filename()) {} + + public: + virtual ~IFormat() = default; + + virtual bool init() = 0; + + virtual bool load2Mem(Program* prog) = 0; + + virtual bool loadSegment(uint8_t* loadAddr, uint64_t fileOffset, uint64_t size) = 0; + + virtual void getAllocInfo(uint64_t& baseSize, uint64_t& baseSizeAligned, uint64_t& sizeAlloc) = 0; + + virtual bool containsAddr(uint64_t vaddr, uint64_t baseVaddr) = 0; + + virtual void setupMissingRelocationHandler(Program* prog, void* relocateHandler, void* payload) = 0; + + virtual Symbols::SymbolInfo getSymbolInfo(uint64_t const relIndex) const = 0; + + virtual void relocate(Program const* prog, uint64_t invalidMemoryAddr, std::string_view libName) = 0; + + virtual std::unordered_map getDebugStrings() const = 0; + virtual std::string collectDebugInfos(std::unordered_map& debugStrings) const = 0; + + virtual void dtInit(uint64_t base, size_t argc = 0, const void* argp = nullptr) const = 0; + + virtual void dtDeinit(uint64_t base) const = 0; + + virtual std::vector> getExecSections() = 0; + + auto getSizeFile() const { return m_fileSize; } + + auto getFilename() const { return m_filename; } + + auto getFilepath() const { return m_path; } +}; \ No newline at end of file diff --git a/core/runtime/formats/ISymbols.h b/core/runtime/formats/ISymbols.h new file mode 100644 index 00000000..a0408b67 --- /dev/null +++ b/core/runtime/formats/ISymbols.h @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include + +struct LibraryId; + +namespace Symbols { +enum class SymbolType { + Unknown, + Func, + Object, + TlsModule, + NoType, +}; + +enum class BindType { Unknown, Local, Global, Weak }; + +struct Info { + std::string_view libraryName; /// Fullname + int libraryVersion; + std::string_view modulName; /// Fullname + int moduleVersionMajor; + int moduleVersionMinor; + + bool operator==(Info const& lhs) const { + return libraryName == lhs.libraryName && libraryVersion >= lhs.libraryVersion && modulName == lhs.modulName && + moduleVersionMajor >= lhs.moduleVersionMajor && moduleVersionMinor >= lhs.moduleVersionMinor; + } +}; + +struct SymbolInfo: public Info { + std::string_view name; + SymbolType type; +}; + +struct Record { + std::string_view name = {}; + uint64_t vaddr = 0; +}; + +struct RelocationInfo { + bool resolved = false; + BindType bind = BindType::Unknown; + SymbolType type = SymbolType::Unknown; + uint64_t value = 0; + uint64_t vaddr = 0; + uint64_t base_vaddr = 0; + std::string_view name; + bool bind_self = false; +}; + +class IResolve { + public: + virtual uintptr_t getAddress(std::string_view symName, std::string_view libName, std::string_view modName) const = 0; + virtual uintptr_t getAddress(std::string_view symName) const = 0; + + virtual std::unordered_map const& getExportedLibs() const = 0; + virtual std::unordered_map const& getImportedLibs() const = 0; +}; + +struct SymbolExport { + struct Symbol { + std::string name; + std::string dbgName; + uint64_t vaddr; + SymbolType type; + }; + + std::string libraryName; + int libraryVersion; + std::string modulName; + int moduleVersionMajor; + int moduleVersionMinor; + + std::unordered_map symbolsMap; + + SymbolExport(std::string_view libraryName, int libraryVersion, std::string_view modulName, int moduleVersionMajor, int moduleVersionMinor) + : libraryName(libraryName), + libraryVersion(libraryVersion), + modulName(modulName), + moduleVersionMajor(moduleVersionMajor), + moduleVersionMinor(moduleVersionMinor) {} +}; + +struct SymbolIntercept { + uint64_t vaddr; + + std::string_view name; + std::string_view libraryName; + std::string_view modulName; +}; +} // namespace Symbols \ No newline at end of file diff --git a/core/runtime/formats/elf64.cpp b/core/runtime/formats/elf64.cpp new file mode 100644 index 00000000..bb07c155 --- /dev/null +++ b/core/runtime/formats/elf64.cpp @@ -0,0 +1,1542 @@ +#include "elf64.h" +#define __APICALL_EXTERN +#include "../runtimeLinker.h" +#undef __APICALL_EXTERN + +#include "../memoryLayout.h" +#include "../util/exceptionHandler.h" +#include "../util/plt.h" +#include "../util/virtualmemory.h" +#include "core/memory/memory.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +LOG_DEFINE_MODULE(ELF64); + +// clang-format off + +using module_func_t = SYSV_ABI int (*)(size_t args, const void* argp); +using module_call_func_t = SYSV_ABI void (*)(); +using module_ini_fini_func_t = SYSV_ABI int (*)(size_t args, const void* argp, module_func_t func); + +// clang-format on + +int jmpModule(uint64_t addr, size_t args, const void* argp, module_func_t func) { + return reinterpret_cast(addr)(args, argp, func); +} + +void callModule(uint64_t addr) { + reinterpret_cast(addr)(); +} + +bool isValid(elf64::CustomHeader const& h) { + if (!std::equal(std::begin(h.ident), std::end(h.ident), std::begin({0x4f, 0x15, 0x3d, 0x1d, 0x00, 0x01, 0x01, 0x12, 0x01, 0x01, 0x00, 0x00})) || + (h.unknown != 0x22)) { + return false; + } + return true; +} + +bool isValid(elf64::ElfHeader const& h) { + if (!std::equal( + std::begin(h.ident), std::begin(h.ident) + 4, + std::begin({uint8_t('\x7f'), uint8_t('E'), uint8_t('L'), uint8_t('F'), uint8_t(2), uint8_t(1), uint8_t(1), uint8_t(9), uint8_t(0), uint8_t(2)})) || + (h.type != elf64::ET_EXEC && h.type != elf64::ET_DYNEXEC && h.type != elf64::ET_DYNAMIC) || (h.machine != elf64::EM_ARCH_X86_64) || (h.version != 1) || + (h.sizePh != sizeof(elf64::ProgramHeader)) || (h.sizeSh > 0 && h.sizeSh != sizeof(elf64::SectionHeader))) { + return false; + } + return true; +} + +uint64_t getAlignedSize(elf64::ProgramHeader const* progHeader) { + return (progHeader->align != 0 ? (progHeader->sizeMem + (progHeader->align - 1)) & ~(progHeader->align - 1) : progHeader->sizeMem); +} + +uint64_t calcBaseSize(elf64::ElfHeader const* elfHeader, elf64::ProgramHeader const* progHeader) { + uint64_t base_size = 0; + for (size_t i = 0; i < elfHeader->numPh; i++) { + if (progHeader[i].sizeMem != 0 && (progHeader[i].type == elf64::PH_LOAD || progHeader[i].type == elf64::PH_OS_RELRO)) { + uint64_t last_addr = progHeader[i].vaddr + getAlignedSize(progHeader + i); + if (last_addr > base_size) { + base_size = last_addr; + } + } + } + return base_size; +} + +int getProtection(uint32_t flags) { + constexpr uint32_t X = (1 << 0); /*Execute*/ + constexpr uint32_t W = (1 << 1); /*Write*/ + constexpr uint32_t R = (1 << 2); /*Read*/ + + int retVal = 0; + if (flags & X) retVal |= SceProtExecute; + if (flags & W) retVal |= SceProtWrite; + if (flags & R) retVal |= SceProtRead; + + return retVal; +} + +constexpr size_t TRAMP_SIZE = 43; + +void createTrampoline64_fs(uint8_t* ptr, int64_t offset, uint64_t addrJmpBack) { + auto savedPtr = ptr; + + // save regs + *ptr++ = 0x57; // push rdi + // - save + + // set param1 (offset) + *ptr++ = 0x48; + *ptr++ = 0xB8; + + *((int64_t*)ptr) = offset; + ptr += 8; + + // call tlsMainGetAddr + *ptr++ = 0xFF; + *ptr++ = 0x15; + + auto offsettlsMainGetAddr = (uint64_t)ptr; + ptr += 4; + // - + + // restore regs + *ptr++ = 0x5F; // pop rdi + // -restore + + // jmp back + *ptr++ = 0xFF; + *ptr++ = 0x25; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x00; + + // set jmp back addr + *((uint64_t*)ptr) = addrJmpBack; + ptr += 8; + + // set tlsMainGetAddr and its offset + *((uint64_t*)ptr) = (uint64_t)tlsMainGetAddr64; + *((int32_t*)offsettlsMainGetAddr) = -4 + (uint64_t)ptr - offsettlsMainGetAddr; +} + +void createTrampoline_fs(uint8_t* ptr, uint8_t dstRegister, bool isWork, int32_t offset, uint64_t addrJmpBack) { + auto savedPtr = ptr; + + // After all the jmps, we end up in this wrapper + bool const isRax = (isWork && (dstRegister == 0xc0)); + + // save regs + *ptr++ = 0x57; // push rdi + if (!isRax) *ptr++ = 0x50; // push rax + // - save + + // set param1 (offset) + *ptr++ = 0x48; + *ptr++ = 0xc7; + *ptr++ = 0xc7; + + *((int32_t*)ptr) = offset; + ptr += 4; + + // call tlsMainGetAddr + *ptr++ = 0xFF; + *ptr++ = 0x15; + + auto offsettlsMainGetAddr = (uint64_t)ptr; + ptr += 4; + // - + + // mov {dstRegister}, rax + if (!isRax) { + *ptr++ = isWork ? 0x48 : 0x49; + *ptr++ = 0x89; + *ptr++ = dstRegister; + } + // -dst + + // restore regs + if (!isRax) *ptr++ = 0x58; // pop rax + *ptr++ = 0x5F; // pop rdi + // -restore + + // jmp back + *ptr++ = 0xFF; + *ptr++ = 0x25; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x00; + *ptr++ = 0x00; + + // set jmp back addr + *((uint64_t*)ptr) = addrJmpBack; + ptr += 8; + + // set tlsMainGetAddr and its offset + *((uint64_t*)ptr) = (uint64_t)tlsMainGetAddr; + *((int32_t*)offsettlsMainGetAddr) = -4 + (uint64_t)ptr - offsettlsMainGetAddr; + ptr += 8; + + assert((ptr - savedPtr) <= TRAMP_SIZE); +} + +uint8_t convReg(uint8_t reg) { + LOG_USE_MODULE(ELF64); + + switch (reg) { + case 0x04: return 0xc0; + case 0x0c: return 0xc1; + case 0x14: return 0xc2; + case 0x1c: return 0xc3; + case 0x24: return 0xc4; + case 0x2c: return 0xc5; + case 0x34: return 0xc6; + case 0x3c: return 0xc7; + default: LOG_CRIT(L"unknown reg %0x| mov {reg}, fs", reg); + } + return 0xc0; +} + +void patchProgram(uint64_t slotAddr, uint64_t const patchAddr) { + uint8_t* ptr = (uint8_t*)patchAddr; + + if (ptr[2] == 0xA1) { // special case: 64 bit offset + int64_t const offset = *((int64_t*)&ptr[3]); + uint64_t const addrJmpBack = (uint64_t)&ptr[11]; + + // Write data to the trampoline data block + createTrampoline64_fs((uint8_t*)*((uint64_t*)slotAddr), offset, addrJmpBack); + + } else { // 32 bit offset + uint8_t const reg = convReg(ptr[3]); + bool const isWork = ptr[1] == 0x48; // rax ..rdi + int32_t const offset = *((int32_t*)&ptr[5]); + uint64_t const addrJmpBack = (uint64_t)&ptr[9]; + + // Write data to the trampoline data block + createTrampoline_fs((uint8_t*)*((uint64_t*)slotAddr), reg, isWork, offset, addrJmpBack); + } + + // create jmp [offset] -> jmp to address + if (ptr[-1] == 0x66 && ptr[-2] == 0x66) { + ptr[-3] = 0x90; + ptr[-2] = 0x90; + ptr[-1] = 0x90; + } + ptr[0] = 0xff; + ptr[1] = 0x25; + + *((uint32_t*)&ptr[2]) = (int64_t)slotAddr - (int64_t)(ptr + 6); +} + +void patchProgram_preprocess(std::list& patchAddresses, uint64_t const progAddr, uint64_t const vaddr, uint64_t const progSize) { + // 64 48 8b 04 25 00 00 00 00 mov rax,QWORD PTR fs:0x0 + // 66 66 66 64 48 8b 04 25 00 00 00 00 mov rax,QWORD PTR fs:0x0 + // 64 48 8b 0c 25 00 00 00 00 mov rcx,QWORD PTR fs:0x0 + // 64 4c 8b 04 25 00 00 00 00 mov r8, QWORD PTR fs:0x0 + // ... + + auto* pStart = (uint8_t*)progAddr; + auto* pEnd = (uint8_t*)(progAddr + progSize); + + for (auto* ptr = pStart; ptr < pEnd; ptr++) { + if (ptr[0] != 0x64) continue; + if (!(ptr[1] == 0x48 || ptr[1] == 0x4c)) continue; + + // Check for the 64 bit version + // 64 48 A1 [XXXXXXXXXXXXXXXX] mov rax,fs:[$XXXXXXXXXXXXXXXX] + if (ptr[2] == 0xA1) { + patchAddresses.push_back((uint64_t)(vaddr + ((uint64_t)ptr - progAddr))); + continue; + } + + if (ptr[2] != 0x8b) continue; + if (!(ptr[3] >= 0x04 && ptr[3] <= 0x3c && ((ptr[3] & 0xf) == 0x04 || (ptr[3] & 0xf) == 0xc))) continue; + if (ptr[4] != 0x25) continue; + + patchAddresses.push_back((uint64_t)(vaddr + ((uint64_t)ptr - progAddr))); + } +} + +elf64::DynamicHeader const* findDynValue(elf64::DynamicHeader const* dynHeaderList, int64_t tag) { + for (const auto* dyn = dynHeaderList; dyn->tag != 0; ++dyn) { + if (dyn->tag == tag) { + return dyn; + } + } + return nullptr; +} + +std::string encodeId(uint16_t in_id) { + std::string ret; + + static const char32_t* str = U"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; + if (in_id < 0x40u) { + ret += str[in_id]; + } else { + if (in_id < 0x1000u) { + ret += str[static_cast(in_id >> 6u) & 0x3fu]; + ret += str[in_id & 0x3fu]; + } else { + ret += str[static_cast(in_id >> 12u) & 0x3fu]; + ret += str[static_cast(in_id >> 6u) & 0x3fu]; + ret += str[in_id & 0x3fu]; + } + } + return ret; +} + +unsigned long elfHash(std::string_view name) { + LOG_USE_MODULE(ELF64); + + unsigned long h = 0, g; + for (auto item: name) { + h = (h << 4) + item; + g = h & 0xf0000000; + if (g > 0) h ^= g >> 24; + h &= ~g; + } + return h; +} + +unsigned long elfHash(std::string_view symName, std::string_view libName, std::string_view modName) { + LOG_USE_MODULE(ELF64); + + unsigned long h = 0, g; + + auto seperator = [&] { + h = (h << 4) + '#'; + g = h & 0xf0000000; + if (g > 0) h ^= g >> 24; + h &= ~g; + }; + + for (auto item: symName) { + h = (h << 4) + item; + g = h & 0xf0000000; + if (g > 0) h ^= g >> 24; + h &= ~g; + } + + seperator(); + for (auto item: libName) { + h = (h << 4) + item; + g = h & 0xf0000000; + if (g > 0) h ^= g >> 24; + h &= ~g; + } + + seperator(); + for (auto item: modName) { + h = (h << 4) + item; + g = h & 0xf0000000; + if (g > 0) h ^= g >> 24; + h &= ~g; + } + + return h; +} + +uint64_t parse_eh_frame_hdr(uint8_t* data) { + uint8_t const framePtrEncoding = data[1]; + if (framePtrEncoding == elf64::DW_EH_PE_omit) return 0; + + uint64_t addr = 0; + bool sign = false; + switch (framePtrEncoding & 0xF) { + case elf64::DW_EH_PE_sdata2: + sign = true; + addr = (int64_t) * (int16_t*)&data[4]; + break; + case elf64::DW_EH_PE_udata2: addr = (uint64_t) * (uint16_t*)&data[4]; break; + case elf64::DW_EH_PE_sdata4: sign = addr = (int64_t) * (int32_t*)&data[4]; break; + case elf64::DW_EH_PE_udata4: addr = *(uint32_t*)&data[4]; break; + case elf64::DW_EH_PE_sdata8: addr = (uint64_t) * (int64_t*)&data[4]; break; + case elf64::DW_EH_PE_udata8: addr = *(uint64_t*)&data[4]; break; + } + + if ((framePtrEncoding & elf64::DW_EH_PE_absptr) > 0) + return addr; + else if ((framePtrEncoding & elf64::DW_EH_PE_datarel) > 0) { + if (!sign) { + return 4 + (uint64_t)data[3] + addr; + } + + return 4 + (uint64_t)data + (int64_t)addr; + + } else if ((framePtrEncoding & elf64::DW_EH_PE_pcrel) > 0) + assert((framePtrEncoding & elf64::DW_EH_PE_pcrel) > 0); + return 0; +} + +uint64_t parse_eh_frame_size(uint8_t* data) { + uint8_t const framePtrEncoding = data[1]; + uint8_t const countEncoding = data[2]; + uint8_t const tableEncoding = data[3]; + + uint8_t* pCounter = 0; + + // get counter offset + switch (framePtrEncoding & 0xF) { + case elf64::DW_EH_PE_sdata2: + case elf64::DW_EH_PE_udata2: pCounter = &data[4 + 2]; break; + case elf64::DW_EH_PE_sdata4: + case elf64::DW_EH_PE_udata4: pCounter = &data[4 + 4]; break; + case elf64::DW_EH_PE_sdata8: + case elf64::DW_EH_PE_udata8: pCounter = &data[4 + 8]; break; + } + + uint64_t counter = 0; + switch (countEncoding & 0xF) { + case elf64::DW_EH_PE_sdata2: + case elf64::DW_EH_PE_udata2: counter = *(uint16_t*)pCounter; break; + case elf64::DW_EH_PE_sdata4: + case elf64::DW_EH_PE_udata4: counter = *(uint32_t*)pCounter; break; + case elf64::DW_EH_PE_sdata8: + case elf64::DW_EH_PE_udata8: counter = *(uint64_t*)pCounter; break; + } + + switch (tableEncoding & 0xF) { + case elf64::DW_EH_PE_sdata2: + case elf64::DW_EH_PE_udata2: counter *= 2 * 2; break; + case elf64::DW_EH_PE_sdata4: + case elf64::DW_EH_PE_udata4: counter *= 2 * 4; break; + case elf64::DW_EH_PE_sdata8: + case elf64::DW_EH_PE_udata8: counter *= 2 * 8; break; + } + + return counter; +} +} // namespace + +elf64::Elf64_Sym const* elf64::SymbolMap::find(std::string_view fullSymbolname) const { + auto nameEnd = fullSymbolname.find('#'); + auto const symbolName = nameEnd != std::string::npos ? fullSymbolname.substr(0, nameEnd) : fullSymbolname; + + const uint32_t hash = elfHash(fullSymbolname); + + const uint32_t nbucket = m_hashTable[0]; + const uint32_t nchain = m_hashTable[1]; + const uint32_t* bucket = &m_hashTable[2]; + const uint32_t* chain = &bucket[nbucket]; + + for (uint32_t i = bucket[hash % nbucket]; i; i = chain[i]) { + auto const res = symbolName.compare(0, symbolName.size(), m_strTable + m_symbolTable[i].name, 0, symbolName.size()); + if (symbolName.compare(0, symbolName.size(), m_strTable + m_symbolTable[i].name, 0, symbolName.size()) == 0) { + return &m_symbolTable[i]; + } + } + return nullptr; +}; + +elf64::Elf64_Sym const* elf64::SymbolMap::find(std::string_view symbolName, std::string_view libName, std::string_view modName) const { + const uint32_t hash = elfHash(symbolName, libName, modName); + + const uint32_t nbucket = m_hashTable[0]; + const uint32_t nchain = m_hashTable[1]; + const uint32_t* bucket = &m_hashTable[2]; + const uint32_t* chain = &bucket[nbucket]; + + for (uint32_t i = bucket[hash % nbucket]; i; i = chain[i]) { + if (symbolName.compare(0, symbolName.size(), m_strTable + m_symbolTable[i].name, 0, symbolName.size()) == 0) { + return &m_symbolTable[i]; + } + } + return nullptr; +}; + +class Parser_ELF64: public IFormat { + std::vector m_fileData; + + // optional + std::unique_ptr m_customHeader; + std::unique_ptr m_segmentHeader; + // - + + size_t m_addrElf = 0; /// Offset for all file addresses + + // ### pointers into m_fileData + + elf64::ElfHeader* m_elfHeader = nullptr; + elf64::ProgramHeader* m_progHeader = nullptr; + elf64::SectionHeader* m_sectionHeader = nullptr; + + std::unique_ptr m_dynamicInfo; + + uint8_t* m_dynamic = nullptr; + uint8_t* m_dynamicData = nullptr; + uint8_t* m_relro = nullptr; + uint64_t m_relroVaddr = 0; + + size_t m_debugSymTabStride = 0; + char const* m_debugStrTab = nullptr; + uint64_t m_debugStrTabSize = 0; + uint8_t const* m_debugSymTab = nullptr; + uint64_t m_debugSymTabSize = 0; + + std::unique_ptr m_file; + + public: + Parser_ELF64(std::filesystem::path&& path, std::unique_ptr&& file): IFormat(std::move(path)), m_file(std::move(file)) {} + + bool init() final; + bool loadSegment(uint8_t* loadAddr, uint64_t fileOffset, uint64_t size) final; + bool load2Mem(Program* prog) final; + void getAllocInfo(uint64_t& baseSize, uint64_t& baseSizeAligned, uint64_t& sizeAlloc) final; + + bool containsAddr(uint64_t vaddr, uint64_t baseVaddr) final; + + void setupMissingRelocationHandler(Program* prog, void* relocateHandler, void* payload) final; + + Symbols::SymbolInfo getSymbolInfo(uint64_t const relIndex) const final; + Symbols::RelocationInfo getRelocationInfo(IRuntimeLinker const* linker, elf64::Elf64_Rela const* r, Program const* prog, + elf64::SymbolMap const& symbolsMap) const; + + void relocate(Program const* prog, uint64_t invalidMemoryAddr, std::string_view libName) final; + + bool isShared() const { return (m_elfHeader->type == elf64::ET_DYNAMIC); } + + bool isNextGen() const { return (m_elfHeader->ident[elf64::EI_ABIVERSION] == 2); } + + uintptr_t resolve(IRuntimeLinker const& linker, std::string_view const symbolName, Symbols::SymbolType const type, uintptr_t addr) const; + + std::unique_ptr parseDynamicInfo(std::wstring_view filename, uint64_t baseVaddr) const; + + LibraryId const* getLibrary(elf64::DynamicInfo const* dynInfo, std::string_view id) const; + elf64::ModuleId const* getModule(elf64::DynamicInfo const* dynInfo, std::string_view id) const; + + uintptr_t getAddress(std::string_view symName, std::string_view libName, std::string_view modName) const final { + auto sym = m_dynamicInfo->symbolsMap.find(symName, libName, modName); + if (sym != nullptr) { + return m_baseVaddr + sym->value; + } + + return 0; + } + + uintptr_t getAddress(std::string_view symName) const final { + for (auto const& [libName, libId]: m_dynamicInfo->exportedLibs) { + for (auto const& [modName, modId]: m_dynamicInfo->exportedMods) { + auto sym = m_dynamicInfo->symbolsMap.find(symName, libName, modName); + if (sym != nullptr) { + return m_baseVaddr + sym->value; + } + } + } + return 0; + } + + virtual std::unordered_map const& getExportedLibs() const final { return m_dynamicInfo->exportedLibs; } + + virtual std::unordered_map const& getImportedLibs() const final { return m_dynamicInfo->importedLibs; } + + virtual std::unordered_map getDebugStrings() const final { + std::unordered_map debugStrings; + if (m_debugSymTab != nullptr) { + debugStrings.reserve(m_debugSymTabSize / m_debugSymTabStride); + for (size_t n = 0; n < m_debugSymTabSize; n += m_debugSymTabStride) { + elf64::Elf64_Sym const* symbol = (elf64::Elf64_Sym const*)&m_debugSymTab[n]; + if (symbol->name > 0 && symbol->value > 0) { + std::string_view const symName = (const char*)&m_debugStrTab[symbol->name]; + debugStrings[symbol->value] = symName; + } + } + } + return debugStrings; + } + + void dtInit(uint64_t base, size_t argc, const void* argp) const final { + LOG_USE_MODULE(ELF64); + + if (m_dynamicInfo->vaddrInit && m_dynamicInfo->vaddrInit.value() != (uint64_t)-1) { + LOG_DEBUG(L"dt_init(%s) offset:0x%08llx", m_filename.c_str(), m_dynamicInfo->vaddrInit.value()); + jmpModule(base + m_dynamicInfo->vaddrInit.value(), argc, argp, nullptr); + } + + if (m_dynamicInfo->vaddrInitArray && m_dynamicInfo->sizeInitArray > 0) { + auto start = (uint64_t const*)(base + m_dynamicInfo->vaddrInitArray.value()); + auto end = (uint64_t const*)(base + m_dynamicInfo->sizeInitArray + m_dynamicInfo->vaddrInitArray.value()); + for (; start != end; ++start) { + if (*start != 0 && *start != (uint64_t)-1) { + LOG_DEBUG(L"initArray(%s) @0x%08llx", m_filename.c_str(), *start); + callModule(*start); + } + } + } + }; + + void dtDeinit(uint64_t base) const final { + if (m_dynamicInfo->vaddrFinArray && m_dynamicInfo->sizeFiniArray > 0) { + auto pStart = (uint64_t const*)(base + m_dynamicInfo->vaddrFinArray.value()); + auto pEnd = (uint64_t const*)(base + m_dynamicInfo->vaddrFinArray.value() + m_dynamicInfo->sizeFiniArray); + for (; pStart != pEnd; ++pStart) { + auto addr = *pStart; + if (addr != 0 && addr != (uint64_t)-1) callModule(addr); + } + } + if (m_dynamicInfo->vaddrFini) { + jmpModule(base + m_dynamicInfo->vaddrFini.value(), 0, nullptr, nullptr); + } + } + + virtual std::string collectDebugInfos(std::unordered_map& debugStrings) const final { + auto dynInfo = parseDynamicInfo(L"", 0); + std::string ret; + + if (debugStrings.empty()) debugStrings = getDebugStrings(); + + auto const& symMap = dynInfo->symbolsMap; + + // Imports + std::unordered_map debugImports; + std::unordered_map debugValues; + auto processImports = [&](elf64::Elf64_Rela const* records, uint64_t numBytes) { + size_t n = 0; + for (auto const* r = records; reinterpret_cast(r) < reinterpret_cast(records) + numBytes; ++r, ++n) { + auto const ri = getRelocationInfo(nullptr, r, nullptr, symMap); + if (ri.name.empty()) continue; + + debugValues[ri.name] = ri.vaddr; + auto itDebugString = debugStrings.find(r->offset); + if (itDebugString != debugStrings.end()) { + debugImports[ri.name] = itDebugString->second; + } + + auto idData = util::splitString(ri.name, '#'); + auto lib = getLibrary(dynInfo.get(), idData[1]); + auto mod = getModule(dynInfo.get(), idData[2]); + + ret += std::to_string(ri.vaddr) + "\t"; + ret += "\t-> id:" + std::to_string(n) + " "; + ret += idData[0]; + ret += "\tlib:"; + ret += lib->name; + ret += " ver:" + std::to_string(lib->version); + ret += " mod:"; + ret += mod->name; + ret += " ver:" + std::to_string(mod->versionMajor) + " " + std::to_string(mod->versionMinor); + ret += '\n'; + } + }; + processImports(dynInfo->relaTable, dynInfo->relaTableTotalSize); + processImports(dynInfo->jmprelaTable, dynInfo->jmprelaTableSize); + // - + + for (size_t n = 0; n < symMap.size(); ++n) { + auto const symbol = symMap.getSymbol(n); + if (symbol.name == 0) continue; + + auto const name = symMap.getName(symbol); + + ret += std::to_string(symbol.value) + "\t"; + auto idData = util::splitString(name, '#'); + if (idData.size() == 3) { + auto lib = getLibrary(dynInfo.get(), idData[1]); + auto mod = getModule(dynInfo.get(), idData[2]); + ret += idData[0]; + ret += "\t\tlib:"; + ret += lib->name; + ret += " ver:" + std::to_string(lib->version); + ret += " mod:"; + ret += mod->name; + ret += " ver:" + std::to_string(mod->versionMajor) + " " + std::to_string(mod->versionMinor); + } else { + ret += name; + } + + if (symbol.value == 0) { + auto itDebug = debugImports.find(name); + if (itDebug != debugImports.end()) { + ret += "| "; + ret += itDebug->second; + } + } else { + auto itDebug = debugStrings.find(symbol.value); + if (itDebug != debugStrings.end()) { + ret += "| "; + ret += itDebug->second; + } + } + + ret += '\n'; + } + return ret; + } + + void dump(std::fstream& outFile, std::fstream& mapFile); + + std::vector> getExecSections() final { + std::vector> ret; + + if (m_segmentHeader) { + for (size_t i = 0; i < m_customHeader->numSegments; ++i) { + auto const& seg = m_segmentHeader[i]; + if ((seg.type & 0x800u) != 0) { + auto const index = ((seg.type >> 20u) & 0xFFFu); + auto const& pHeaderCur = m_progHeader[index]; + if ((getProtection(pHeaderCur.flags) & SceProtExecute) > 0) { + ret.push_back({pHeaderCur.vaddr, pHeaderCur.sizeMem}); + return ret; + } + } + } + } else { + for (size_t n = 0; n < m_elfHeader->numSh; ++n) { + if (m_sectionHeader[n].type == 0x1 && (m_sectionHeader[n].flags & 0x4) > 0) { + ret.push_back({m_sectionHeader[n].addr, m_sectionHeader[n].size}); + } + } + } + return ret; + } +}; + +uintptr_t Parser_ELF64::resolve(IRuntimeLinker const& linker, std::string_view const symbolName, Symbols::SymbolType const type, uintptr_t addr) const { + LOG_USE_MODULE(ELF64); + + auto idData = util::splitString(symbolName, '#'); + + if (idData.size() == 3) { + auto lib = getLibrary(m_dynamicInfo.get(), idData[1]); + auto mod = getModule(m_dynamicInfo.get(), idData[2]); + if (lib != nullptr || mod != nullptr) { + + auto iResolve = linker.getIResolve(lib->name); + if (iResolve != nullptr) { + uintptr_t vaddr = iResolve->getAddress(idData[0], lib->name, mod->name); + + if (vaddr != 0) { + vaddr = linker.checkIntercept(vaddr, idData[0], lib->name, mod->name); + // LOG_DEBUG(L"Symbol %S vaddr:0x%08llx lib:%S(%d) mod:%S(%d %d) @0x%08llx", symbolName.data(), vaddr, lib->name.data(), lib->version, + // mod->name.data(), + // mod->versionMajor, mod->versionMinor, addr); + return vaddr; + } + } + + // Trace missing function + if (!__Log::isIgnored(LOG_GET_MODULE(ELF64), __Log::eTrace_Level::trace)) { + LOG_TRACE(L"Missing: Name:%S @%#08llx Lib:%S(%d) Mod:%S(%d)", symbolName.data(), addr, lib->name.data(), lib->version, mod->name.data(), + mod->versionMajor, mod->versionMinor); + } + // - + } else { + LOG_CRIT(L"lib or mod is null"); + } + } else { + // check intern + auto iResolve = linker.getIResolve("intern"); + if (iResolve != nullptr) { + uintptr_t vaddr = iResolve->getAddress(idData[0], "intern", "intern"); + return vaddr; + } + } + + return 0; +} + +Symbols::RelocationInfo Parser_ELF64::getRelocationInfo(IRuntimeLinker const* linker, elf64::Elf64_Rela const* r, Program const* prog, + elf64::SymbolMap const& symbolsMap) const { + LOG_USE_MODULE(ELF64); + Symbols::RelocationInfo ret; + + auto type = r->getType(); + auto symbol = r->getSymbol(); + int64_t addend = r->addend; + ret.base_vaddr = prog != nullptr ? prog->baseVaddr : 0; + ret.vaddr = ret.base_vaddr + r->offset; + ret.bind_self = false; + + switch (type) { + case elf64::R_X86_64_GLOB_DAT: + case elf64::R_X86_64_JUMP_SLOT: addend = 0; [[fallthrough]]; + case elf64::R_X86_64_64: { + auto& sym = symbolsMap.getSymbol(symbol); + auto bind = sym.getBind(); + auto sym_type = sym.getType(); + uint64_t symbolVaddr = 0; + + switch (sym_type) { + case elf64::STT_NOTYPE: ret.type = Symbols::SymbolType::NoType; break; + case elf64::STT_FUNC: ret.type = Symbols::SymbolType::Func; break; + case elf64::STT_OBJECT: ret.type = Symbols::SymbolType::Object; break; + default: LOG_CRIT(L"unknown symbol type: %d\n", (int)sym_type); + } + switch (bind) { + case elf64::STB_LOCAL: + symbolVaddr = ret.base_vaddr + sym.value; + ret.bind = Symbols::BindType::Local; + break; + case elf64::STB_GLOBAL: ret.bind = Symbols::BindType::Global; [[fallthrough]]; + case elf64::STB_WEAK: { + ret.bind = (ret.bind == Symbols::BindType::Unknown ? Symbols::BindType::Weak : ret.bind); + ret.name = symbolsMap.getName(sym); + + if (linker != nullptr) symbolVaddr = resolve(*linker, ret.name, ret.type, ret.vaddr); + } break; + default: LOG_CRIT(L"unknown bind: %d", (int)bind); + } + ret.resolved = (symbolVaddr != 0); + ret.value = (ret.resolved ? symbolVaddr + addend : 0); + } break; + case elf64::R_X86_64_RELATIVE: + ret.value = ret.base_vaddr + addend; + ret.resolved = true; + break; + case elf64::R_X86_64_DTPMOD64: { + ret.value = prog != nullptr ? (uint64_t)(prog->tls.index) : 0; + ret.resolved = true; + ret.type = Symbols::SymbolType::TlsModule; + ret.bind = Symbols::BindType::Local; + } break; + case elf64::R_X86_64_TPOFF64: { + auto& sym = symbolsMap.getSymbol(symbol); + ret.value = sym.value + r->addend - prog->tls.offset; + ret.resolved = true; + ret.type = Symbols::SymbolType::TlsModule; + ret.bind = Symbols::BindType::Local; + } break; + default: LOG_CRIT(L"unknown type: %d", (int)type); + } + + return ret; +} + +Symbols::SymbolInfo Parser_ELF64::getSymbolInfo(uint64_t const relIndex) const { + + elf64::Elf64_Rela const* r = m_dynamicInfo->jmprelaTable + relIndex; + + auto const& symbols = m_dynamicInfo->symbolsMap; + auto const& sym = symbols.getSymbol(r->getSymbol()); + auto const symbolName = symbols.getName(sym); + + auto idData = util::splitString(symbolName, '#'); + Symbols::SymbolInfo info = { + .name = idData[0], + .type = Symbols::SymbolType::Unknown, + }; + + if (idData.size() == 3) { + auto lib = getLibrary(m_dynamicInfo.get(), idData[1]); + auto mod = getModule(m_dynamicInfo.get(), idData[2]); + + if (lib != nullptr) { + info.libraryName = lib->name; + info.libraryVersion = lib->version; + } + if (mod != nullptr) { + info.modulName = mod->name; + info.moduleVersionMajor = mod->versionMajor; + info.moduleVersionMinor = mod->versionMinor; + } + } + return info; +} + +LibraryId const* Parser_ELF64::getLibrary(elf64::DynamicInfo const* dynInfo, std::string_view id) const { + { + auto& importLibs = dynInfo->importedLibs; + + auto it = std::find_if(importLibs.begin(), importLibs.end(), [id](auto const& lib) { return lib.second.id == id; }); + if (it != importLibs.end()) { + return &(it->second); + } + } + { + auto& exportLibs = dynInfo->exportedLibs; + + auto it = std::find_if(exportLibs.begin(), exportLibs.end(), [id](auto const& lib) { return lib.second.id == id; }); + if (it != exportLibs.end()) { + return &(it->second); + } + } + return nullptr; +} + +elf64::ModuleId const* Parser_ELF64::getModule(elf64::DynamicInfo const* dynInfo, std::string_view id) const { + { + auto& importedMods = dynInfo->importedMods; + + auto it = std::find_if(importedMods.begin(), importedMods.end(), [id](auto const& lib) { return lib.second.id == id; }); + if (it != importedMods.end()) { + return &(it->second); + } + } + { + auto& exportedMods = dynInfo->exportedMods; + + auto it = std::find_if(exportedMods.begin(), exportedMods.end(), [id](auto const& lib) { return lib.second.id == id; }); + if (it != exportedMods.end()) { + return &(it->second); + } + } + + return nullptr; +} + +bool Parser_ELF64::containsAddr(uint64_t vaddr, uint64_t baseVaddr) { + LOG_USE_MODULE(ELF64); + if (m_intState != util::InterfaceState::Init) { + LOG_CRIT(L"not init"); + return false; + } + + for (size_t i = 0; i < m_elfHeader->numPh; i++) { + if (m_progHeader[i].sizeMem != 0 && (m_progHeader[i].type == elf64::PH_LOAD || m_progHeader[i].type == elf64::PH_OS_RELRO)) { + uint64_t segment_addr = m_progHeader[i].vaddr + baseVaddr; + uint64_t segment_size = getAlignedSize(&m_progHeader[i]); + + if (vaddr >= segment_addr && vaddr < segment_addr + segment_size) { + return true; + } + } + } + return false; +} + +bool Parser_ELF64::loadSegment(uint8_t* loadAddr, uint64_t fileOffset, uint64_t size) { + memcpy(loadAddr, m_fileData.data() + fileOffset - m_addrElf, size); + return true; +} + +bool Parser_ELF64::init() { + LOG_USE_MODULE(ELF64); + [[unlikely]] if (m_intState != util::InterfaceState::NotInit) { + LOG_CRIT(L"Already init"); + return false; + } + + util::IntStateErrorOnReturn intStateBomb(m_intState); + + m_fileSize = std::filesystem::file_size(m_path); + + LOG_INFO(L"(%s) size: %u", m_path.c_str(), m_fileSize); + + if ((sizeof(elf64::CustomHeader)) > m_fileSize) { + LOG_CRIT(L"invalid filesize for (%s)", m_path.c_str()); + return false; + } + + auto m_customHeader = std::make_unique(); + + std::unique_ptr segmentHeader; + // Check if "custom header" is present + { + if (!util::readFile(m_file.get(), m_filename.c_str(), reinterpret_cast(m_customHeader.get()), sizeof(elf64::CustomHeader))) { + return false; + } + bool valid = isValid(*m_customHeader); + LOG_DEBUG(L"m_customHeader %s[%S]: s1:%u, s2:%u, sF:%llu, numS:%u, unk:%u, pad:%lu", m_filename.c_str(), util::getBoolStr(valid), m_customHeader->size1, + m_customHeader->size2, m_customHeader->sizeFile, m_customHeader->numSegments, m_customHeader->unknown, m_customHeader->pad); + + if (!valid) { + LOG_INFO(L"%s no custom-header", m_filename.c_str()); + m_customHeader.reset(); + m_file->seekg(0); + } else { + // Parse SegmentHeader + m_segmentHeader = std::make_unique(m_customHeader->numSegments); + if (!util::readFile(m_file.get(), m_filename.c_str(), reinterpret_cast(m_segmentHeader.get()), + sizeof(elf64::SegmentHeader) * m_customHeader->numSegments)) { + return false; + } + + for (size_t n = 0; n < m_customHeader->numSegments; ++n) { + LOG_DEBUG(L"segHeader[%lu] %s: type:0x%08llx, offset:0x%08llx, sizeCom:%llu, " + L"sizeDecom:%llu", + n, m_filename.c_str(), m_segmentHeader[n].type, m_segmentHeader[n].offset, m_segmentHeader[n].sizeCom, m_segmentHeader[n].sizeDecom); + } + // - + } + } + // - Check custom header + + // Parse ELF Header + m_addrElf = m_file->tellg(); + uint64_t actualFileSize = m_fileSize; + + // Get actual file size after decompress etc (needed if customheader available) + { + LOG_TRACE(L"Address ElfHeader %s: 0x%08llx", m_filename.c_str(), m_addrElf); + auto elfHeader = std::make_unique(); + + if (!util::readFile(m_file.get(), m_filename.c_str(), reinterpret_cast(elfHeader.get()), sizeof(elf64::ElfHeader))) { + return false; + } + bool valid = isValid(*elfHeader); + + LOG_TRACE(L"ElfHeader %s[%S]: type:0x%02x, machine:0x%0x, ver:%lu, entry:0x%08llx, " + L"phOff:0x%08llx, shOff:0x%08llx, flags:%lu, sizeEh:%u, sizePh:%u, " + L"numPh:%u, sizeSH:%u, numSH:%u, indexSh:%u ", + m_filename.c_str(), util::getBoolStr(valid), elfHeader->type, elfHeader->machine, elfHeader->version, elfHeader->entry, elfHeader->phOff, + elfHeader->shOff, elfHeader->flags, elfHeader->sizeEh, elfHeader->sizePh, elfHeader->numPh, elfHeader->sizeSh, elfHeader->numSh, + elfHeader->indexSh); + if (!valid) { + LOG_CRIT(L"elfHeader not valid %s: size(PH):%lu, size(SH):%lu, ident: %0x%0x%0x%0x ", m_filename.c_str(), sizeof(elf64::ProgramHeader), + sizeof(elf64::SectionHeader), elfHeader->ident[0], elfHeader->ident[1], elfHeader->ident[2], elfHeader->ident[3]); + return false; + } + // - Parse ELF Header + + // Parse ProgramHeader + m_file->seekg(m_addrElf + elfHeader->phOff); + auto progHeader = std::make_unique(elfHeader->numPh); + if (!util::readFile(m_file.get(), m_filename.c_str(), reinterpret_cast(progHeader.get()), sizeof(elf64::ProgramHeader) * elfHeader->numPh)) { + return false; + } + + for (size_t n = 0; n < elfHeader->numPh; ++n) { + auto& item = progHeader[n]; + uint64_t const endAddr = item.offset + item.sizeFile; + + if (actualFileSize < endAddr) actualFileSize = endAddr; + + LOG_DEBUG(L"item[%lu] %s: type:0x%04x, flags:%u, offset:0x%08llx, " + L"vaddr:0x%08llx, paddr:0x%08llx, sizeFile:%llu, sizeMem:%llu, " + L"align:%llu", + n, m_filename.c_str(), item.type, item.flags, item.offset, item.vaddr, item.paddr, item.sizeFile, item.sizeMem, item.align); + } + } + // - ProgramHeader + + // Read file and decompress etc if needed (custom segments) + m_file->seekg(m_addrElf, std::ios_base::beg); + m_fileData.resize(actualFileSize - m_addrElf); + if (!util::readFile(m_file.get(), m_filename.c_str(), (char*)m_fileData.data(), m_fileSize - m_addrElf)) { + return false; + } + + // set pointers + m_elfHeader = (elf64::ElfHeader*)&m_fileData[0]; + m_progHeader = (elf64::ProgramHeader*)&m_fileData[m_elfHeader->phOff]; + + // Section Header + if (m_elfHeader->numSh > 0 && m_elfHeader->shOff < m_fileData.size() && (elf64::SectionHeader*)&m_fileData[m_elfHeader->shOff]) { + m_sectionHeader = (elf64::SectionHeader*)&m_fileData[m_elfHeader->shOff]; + + // Strings available -> load + if (m_sectionHeader->size > 0 && !__Log::isIgnored(LOG_GET_MODULE(ELF64), __Log::eTrace_Level::trace)) { + char const* debugData = (char const*)(m_fileData.data() + m_sectionHeader[m_elfHeader->indexSh].offset); // todo test if offset is needed + + for (size_t n = 0; n < m_elfHeader->numSh; ++n) { + auto const& header = m_sectionHeader[n]; + std::string_view const sectionName = debugData + m_sectionHeader[n].name; + + if (m_sectionHeader[n].type == 0x2 && sectionName.compare(".symtab") == 0) { + m_debugSymTabStride = header.entrySize; + m_debugSymTab = m_fileData.data() + header.offset; // todo test if offset is needed + m_debugSymTabSize = header.size; + } else if (m_sectionHeader[n].type == 0x3 && sectionName.compare(".strtab") == 0) { + m_debugStrTab = (char const*)(m_fileData.data() + header.offset); // todo test if offset is needed + m_debugStrTabSize = header.size; + } + + LOG_DEBUG(L"sectionHeader %s: name:%S, type:0x%04x, flags:%u, " + L"addr:0x%08llx, " + L"offset:0x%08llx, size:0x%08llx, link:%lu, info:%lu, addAlign:%llu, " + L"entrySize:%llu", + m_filename.c_str(), debugData + header.name, header.type, header.flags, header.addr, header.offset, header.size, header.link, header.info, + header.addrAlign, header.entrySize); + } + } + // - Section Header + } + // - set pointers + + // segment unpack + if (m_customHeader) { + for (size_t i = 0; i < m_customHeader->numSegments; ++i) { + auto const& segHeader = m_segmentHeader[i]; + if ((segHeader.type & elf64::SF_BFLG) != 0) { + assert((segHeader.type & elf64::SF_ENCR) == 0); + assert((segHeader.type & elf64::SF_DFLG) == 0); + assert(segHeader.sizeCom == segHeader.sizeDecom); + + auto const index = ((segHeader.type >> 20u) & 0xFFFu); + auto const& progHeader = m_progHeader[index]; + + uint8_t* dst = &m_fileData[progHeader.offset - m_addrElf]; + + // Read from file (m_fileData may already be overwritten by prev calls) + m_file->seekg(segHeader.offset, std::ios_base::beg); + if (!util::readFile(m_file.get(), m_filename.c_str(), (char*)dst, segHeader.sizeDecom)) { + return false; + } + } + } + } + // - + + intStateBomb.defuse(); + m_intState = util::InterfaceState::Init; + LOG_INFO(L"<-- init(%s)", m_filename.c_str()); + + return true; +} + +void Parser_ELF64::getAllocInfo(uint64_t& baseSize, uint64_t& baseSizeAligned, uint64_t& sizeAlloc) { + baseSize = calcBaseSize(m_elfHeader, m_progHeader); + baseSizeAligned = (baseSize & ~(static_cast(0x1000) - 1)) + 0x1000; + sizeAlloc = baseSizeAligned + ExceptionHandler::getAllocSize(); +} + +bool Parser_ELF64::load2Mem(Program* prog) { + LOG_USE_MODULE(ELF64); + if (m_intState != util::InterfaceState::Init) { + LOG_CRIT(L"Not Init"); + return false; + } + + std::list fsPatchAddrList; + + // ### Preparse patching + for (size_t n = 0; n < m_elfHeader->numPh; ++n) { + auto& progHeader = m_progHeader[n]; + + if ((progHeader.type == elf64::PH_LOAD || progHeader.type == elf64::PH_OS_RELRO) && progHeader.sizeMem != 0 && progHeader.flags != 0) { + auto const mode = getProtection(progHeader.flags); + + if (memory::isExecute(mode)) { + patchProgram_preprocess(fsPatchAddrList, (uint64_t)(m_fileData.data() + progHeader.offset - m_addrElf), progHeader.vaddr, progHeader.sizeFile); + } + } + } + // --- + + bool const shared = isShared(); + // bool const nextGen = isNextGen(); + + prog->baseVaddr = memory::alloc(prog->desiredBaseAddr, prog->baseSizeAligned + /*fs_table: */ sizeof(uint64_t) * (1 + fsPatchAddrList.size()), + SceProtExecute | SceProtRead | SceProtWrite); + + printf("[%d] %S| Image allocation, addr:0x%08llx size:0x%08llx\n", prog->id, m_filename.c_str(), prog->baseVaddr, prog->allocSize); + LOG_DEBUG(L"[%d] %s| Image allocation, addr:0x%08llx(0x%08llx) size:0x%08llx", prog->id, m_filename.c_str(), prog->baseVaddr, prog->desiredBaseAddr, + prog->allocSize); + + m_baseVaddr = prog->baseVaddr; + ExceptionHandler::install(prog->baseVaddr, (prog->baseVaddr + prog->baseSizeAligned) - ExceptionHandler::getAllocSize(), prog->baseSize); + + strcpy_s(prog->moduleInfoEx.name, prog->filename.string().c_str()); + prog->moduleInfoEx.id = prog->id; + + // Load Segments + for (size_t n = 0; n < m_elfHeader->numPh; ++n) { + auto& progHeader = m_progHeader[n]; + + if (n < 3) { + auto& seg = prog->moduleInfoEx.segments[n]; + seg.address = progHeader.vaddr + prog->baseVaddr; + seg.prot = progHeader.flags; + seg.size = progHeader.sizeMem; + + prog->moduleInfoEx.segment_count = 1 + n; + } + + // Load data to virtual + if ((progHeader.type == elf64::PH_LOAD || progHeader.type == elf64::PH_OS_RELRO) && progHeader.sizeMem != 0 && progHeader.flags != 0) { + auto const mode = getProtection(progHeader.flags); + uint64_t const addr = util::alignDown(progHeader.vaddr + prog->baseVaddr, progHeader.align); + uint64_t const sizeMemory = getAlignedSize(&progHeader); + + LOG_INFO(L"%s| LoadSegment[%u]: type:0x%08x addr:0x%08llx size:0x%08llx(align:0x%08llx) mode:0x%x", prog->filename.c_str(), n, progHeader.type, addr, + progHeader.sizeMem, sizeMemory, mode); + if (!loadSegment((uint8_t*)addr, progHeader.offset, progHeader.sizeFile)) { + LOG_CRIT(L"loadSegment error (%s)", prog->filename.c_str()); + } + memset((uint8_t*)(addr + progHeader.sizeFile), 0, sizeMemory - progHeader.sizeFile); // fill if needed + } + // -load virtual + + if (progHeader.type == elf64::PH_GNU_EH_HEADER) { + prog->moduleInfoEx.eh_frame_hdr_addr = (void*)(progHeader.vaddr + prog->baseVaddr); + prog->moduleInfoEx.eh_frame_addr = (void*)parse_eh_frame_hdr((uint8_t*)prog->moduleInfoEx.eh_frame_hdr_addr); + prog->moduleInfoEx.eh_frame_size = parse_eh_frame_size((uint8_t*)prog->moduleInfoEx.eh_frame_hdr_addr); + + LOG_DEBUG(L"%s| EH_FRAME_HEADER[%u] addr:0x%08llx frame@0x%08llx size:0x%08llx", prog->filename.c_str(), n, prog->moduleInfoEx.eh_frame_hdr_addr, + prog->moduleInfoEx.eh_frame_addr, prog->moduleInfoEx.eh_frame_size); + } else if (progHeader.type == elf64::PH_TLS) { + assert(progHeader.vaddr < prog->baseSize); + + prog->tls.vaddrImage = progHeader.vaddr + prog->baseVaddr; + prog->tls.sizeImage = progHeader.sizeMem; + prog->tls.alignment = progHeader.align; + + prog->moduleInfoEx.tls_init_addr = (void*)(progHeader.vaddr + prog->baseVaddr); + prog->moduleInfoEx.tls_init_size = progHeader.sizeFile; + prog->moduleInfoEx.tls_size = progHeader.sizeMem; + prog->moduleInfoEx.tls_align = progHeader.align; + + LOG_DEBUG(L"%s| TLS[%u] addr:0x%08llx size:0x%08llx", prog->filename.c_str(), n, prog->tls.vaddrImage, prog->tls.sizeImage); + } else if (progHeader.type == elf64::PH_OS_PROCPARAM) { + assert(prog->procParamVaddr == 0); + assert(progHeader.vaddr < prog->baseSize); + + prog->procParamVaddr = progHeader.vaddr + prog->baseVaddr; + } else if (progHeader.type == elf64::PH_DYNAMIC) { + m_dynamic = (uint8_t*)(m_fileData.data() + progHeader.offset - m_addrElf); + LOG_DEBUG(L"%s| Dynamic[%u] addr:0x%08llx, size:0x%08llx", m_filename.c_str(), n, (uint64_t)m_dynamic, progHeader.sizeFile); + } else if (progHeader.type == elf64::PH_OS_DYNLIBDATA) { + m_dynamicData = (uint8_t*)(m_fileData.data() + progHeader.offset - m_addrElf); + LOG_DEBUG(L"%s| DynamicData[%u] addr:0x%08llx, size:0x%08llx", m_filename.c_str(), n, (uint64_t)m_dynamicData, progHeader.sizeFile); + } else if (progHeader.type == elf64::PH_OS_RELRO) { + m_relro = (uint8_t*)(m_fileData.data() + progHeader.offset - m_addrElf); + m_relroVaddr = progHeader.vaddr + prog->baseVaddr; + LOG_DEBUG(L"%s| relro[%u] addr:0x%08llx, size:0x%08llx", m_filename.c_str(), n, (uint64_t)m_dynamicData, progHeader.sizeFile); + } + } + // - + + // ### PATCHING + // patch fs + if (!fsPatchAddrList.empty()) { + uint64_t const addrFsList = util::alignUp(prog->baseVaddr + prog->baseSizeAligned, 64); + prog->trampolines_fs.resize(TRAMP_SIZE * fsPatchAddrList.size()); + + LOG_DEBUG(L"tramp(fs) table:0x%08llx data:0x%08llx", addrFsList, (uint64_t)prog->trampolines_fs.data()); + + size_t n = 0; + + auto trampSlot = prog->trampolines_fs.data(); + auto pAddrFsList = (uint64_t*)addrFsList; + + for (auto const& fsCall: fsPatchAddrList) { + *pAddrFsList = (uint64_t)trampSlot; // put addr in table + patchProgram((uint64_t)pAddrFsList, prog->baseVaddr + fsCall); + + ++pAddrFsList; + trampSlot += TRAMP_SIZE; + ++n; + } + + memory::protect((uint64_t)prog->trampolines_fs.data(), prog->trampolines_fs.size(), + SceProtExecute | SceProtRead | SceProtWrite); // Protecting heap! Ok, just adding exec + flushInstructionCache((uint64_t)prog->trampolines_fs.data(), prog->trampolines_fs.size()); + } + // --- PATCHING + + // Set the protection + for (size_t n = 0; n < m_elfHeader->numPh; ++n) { + auto& progHeader = m_progHeader[n]; + + if ((progHeader.type == elf64::PH_LOAD || progHeader.type == elf64::PH_OS_RELRO) && progHeader.sizeMem != 0 && progHeader.flags != 0) { + auto const mode = getProtection(progHeader.flags); + uint64_t const addr = util::alignDown(progHeader.vaddr + prog->baseVaddr, progHeader.align); + uint64_t const sizeMemory = getAlignedSize(&progHeader); + + memory::protect(addr, sizeMemory, mode); // Protect Memory + flushInstructionCache(addr, sizeMemory); + } + } + // - protect + + // Is main program? + if (!shared) { + prog->isMainProg = true; + } + + m_dynamicInfo = parseDynamicInfo(prog->filename.c_str(), prog->baseVaddr); + if (m_dynamicInfo != nullptr) { + prog->entryOffAddr = m_elfHeader->entry; + if (m_dynamicInfo->vaddrInit) prog->moduleInfoEx.init_proc_addr = *m_dynamicInfo->vaddrInit; + if (m_dynamicInfo->vaddrFini) prog->moduleInfoEx.fini_proc_addr = *m_dynamicInfo->vaddrFini; + } + LOG_INFO(L"<-- load2Mem %s", m_filename.c_str()); + + return true; +} + +std::unique_ptr Parser_ELF64::parseDynamicInfo(std::wstring_view filename, uint64_t baseVaddr) const { + LOG_USE_MODULE(ELF64); + LOG_TRACE(L"--> parseDynamicInfo %s", filename.data()); + + if (m_dynamicData == nullptr) { + LOG_WARN(L"%s no dynamicData", filename.data()); + return nullptr; + } + + // find dynamicData by tag + auto getDynamicData = [this, baseVaddr](auto const tagOs, auto const tag) -> elf64::DynamicHeader const* { + if (const auto* dyn = findDynValue(reinterpret_cast(m_dynamic), tagOs); dyn != nullptr) { + return reinterpret_cast(m_dynamicData + dyn->_un.ptr); + } else if (const auto* dyn = findDynValue(reinterpret_cast(m_dynamic), tag); dyn != nullptr) { + return reinterpret_cast(baseVaddr + dyn->_un.ptr); + } + return nullptr; + }; + + auto getDynamicDataPtr = [this](auto const tag) -> uint64_t { + if (const auto* dyn = findDynValue(reinterpret_cast(m_dynamic), tag); dyn != nullptr) { + return dyn->_un.ptr; + } + return NULL; + }; + + auto getDynamicDataValue = [this](auto const tag1, auto const tag2) -> uint64_t { + uint64_t ret = 0; + if (tag1 != 0) { + if (const auto* dyn = findDynValue(reinterpret_cast(m_dynamic), tag1); dyn != nullptr) { + ret = dyn->_un.value; + } + } + + if (ret == 0 && tag2 != 0) { + if (const auto* dyn = findDynValue(reinterpret_cast(m_dynamic), tag2); dyn != nullptr) { + ret = dyn->_un.value; + } + } + return ret; + }; + + auto getLibs = [this](char const* names, auto const tag1, auto const tag2) { + std::unordered_map libs; + + for (const auto* dyn = reinterpret_cast(m_dynamic); dyn->tag != 0; ++dyn) { + if (dyn->tag == tag1 || dyn->tag == tag2) { + LibraryId lib { + .id = encodeId(static_cast((dyn->_un.value >> 48u) & 0xffffu)), + .version = static_cast((dyn->_un.value >> 32u) & 0xffffu), + .name = names + (dyn->_un.value & 0xffffffff), + }; + libs.insert({lib.name, std::move(lib)}); + } + } + return libs; + }; + + auto getMods = [this](char const* names, auto const tag1, auto const tag2) { + std::unordered_map mods; + + for (const auto* dyn = reinterpret_cast(m_dynamic); dyn->tag != 0; ++dyn) { + if (dyn->tag == tag1 || dyn->tag == tag2) { + std::string_view modName = names + (dyn->_un.value & 0xffffffff); + + mods.insert({modName, elf64::ModuleId { + .id = encodeId(static_cast((dyn->_un.value >> 48u) & 0xffffu)), + .versionMajor = static_cast((dyn->_un.value >> 40u) & 0xffu), + .versionMinor = static_cast((dyn->_un.value >> 32u) & 0xffu), + .name = modName, + }}); + } + } + return mods; + }; + + auto const hashTable = (const uint32_t*)getDynamicData(elf64::DT_OS_HASH, elf64::DT_HASH); + auto const hashTableSize = getDynamicDataValue(elf64::DT_OS_HASHSZ, 0); + LOG_DEBUG(L"(%s) hashTable addr:%#08llx size:%llu", filename.data(), reinterpret_cast(hashTable), hashTableSize); + + auto const strTable = (const char*)getDynamicData(elf64::DT_OS_STRTAB, elf64::DT_STRTAB); + auto const strTableSize = getDynamicDataValue(elf64::DT_OS_STRSZ, elf64::DT_STRSZ); + LOG_DEBUG(L"(%s) strTable addr:%#08llx size:%llu", filename.data(), (uint64_t)(strTable), strTableSize); + + auto const symbolTable = (elf64::Elf64_Sym const*)getDynamicData(elf64::DT_OS_SYMTAB, elf64::DT_SYMTAB); + auto const symbolTableSizeTotal = getDynamicDataValue(elf64::DT_OS_SYMTABSZ, 0); + auto const symbolTableSizeEntry = getDynamicDataValue(elf64::DT_OS_SYMENT, elf64::DT_SYMENT); + LOG_DEBUG(L"(%s) symbolTable addr:%#08llx size:%llu sizeEntry%llu", filename.data(), reinterpret_cast(symbolTable), symbolTableSizeTotal, + symbolTableSizeEntry); + + auto dynInfo = std::make_unique(elf64::SymbolMap(hashTable, hashTableSize, strTable, strTableSize, symbolTable, + symbolTable != nullptr ? symbolTableSizeTotal / symbolTableSizeEntry : 0)); + + dynInfo->vaddrInit = getDynamicDataPtr(elf64::DT_INIT); + dynInfo->vaddrFini = getDynamicDataPtr(elf64::DT_FINI); + + dynInfo->vaddrInitArray = getDynamicDataPtr(elf64::DT_INIT_ARRAY); + dynInfo->vaddrFinArray = getDynamicDataPtr(elf64::DT_FINI_ARRAY); + dynInfo->vaddrPreinitArray = getDynamicDataPtr(elf64::DT_PREINIT_ARRAY); + dynInfo->sizePreinitArray = getDynamicDataPtr(elf64::DT_PREINIT_ARRAYSZ); + dynInfo->sizeInitArray = getDynamicDataPtr(elf64::DT_INIT_ARRAYSZ); + dynInfo->sizeFiniArray = getDynamicDataPtr(elf64::DT_FINI_ARRAYSZ); + dynInfo->vaddrPltgot = getDynamicDataPtr(elf64::DT_OS_PLTGOT); + if (dynInfo->vaddrPltgot == 0) dynInfo->vaddrPltgot = getDynamicDataPtr(elf64::DT_PLTGOT); + + if (getDynamicDataValue(elf64::DT_OS_PLTREL, elf64::DT_PLTREL) == elf64::DT_RELA) { + dynInfo->jmprelaTable = (elf64::Elf64_Rela const*)getDynamicData(elf64::DT_OS_JMPREL, elf64::DT_JMPREL); + dynInfo->jmprelaTableSize = getDynamicDataValue(elf64::DT_OS_PLTRELSZ, elf64::DT_PLTRELSZ); + LOG_DEBUG(L"(%s) jmprelaTable addr:%#08llx size:%llu", filename.data(), (uint64_t)(dynInfo->jmprelaTable), dynInfo->jmprelaTableSize); + } + + dynInfo->relaTable = (elf64::Elf64_Rela const*)getDynamicData(elf64::DT_OS_RELA, elf64::DT_RELA); + dynInfo->relaTableTotalSize = getDynamicDataValue(elf64::DT_OS_RELASZ, elf64::DT_RELASZ); + dynInfo->relaTableEntrySize = getDynamicDataValue(elf64::DT_OS_RELAENT, elf64::DT_RELAENT); + LOG_DEBUG(L"(%s) relaTable %#08llx size:%llu entrysize:%llu", filename.data(), (uint64_t)(dynInfo->relaTable), dynInfo->relaTableTotalSize, + dynInfo->relaTableEntrySize); + + dynInfo->relativeCount = getDynamicDataValue(elf64::DT_RELACOUNT, 0); + dynInfo->debug = getDynamicDataValue(elf64::DT_DEBUG, 0); + dynInfo->flags = getDynamicDataValue(elf64::DT_FLAGS, 0); + dynInfo->textrel = getDynamicDataValue(elf64::DT_TEXTREL, 0); + LOG_DEBUG(L"(%s) relativeCount:%08llx, debug:%08llx, flags:%08llx, textrel:%08llx", filename.data(), dynInfo->relativeCount, dynInfo->debug, dynInfo->flags, + dynInfo->textrel); + + // ### Libs + dynInfo->importedLibs = getLibs(strTable, elf64::DT_OS_IMPORT_LIB, elf64::DT_OS_IMPORT_LIB_1); + dynInfo->exportedLibs = getLibs(strTable, elf64::DT_OS_EXPORT_LIB, elf64::DT_OS_EXPORT_LIB_1); + + // ### Modules + dynInfo->importedMods = getMods(strTable, elf64::DT_OS_NEEDED_MODULE, elf64::DT_OS_NEEDED_MODULE_1); + dynInfo->exportedMods = getMods(strTable, elf64::DT_OS_MODULE_INFO, elf64::DT_OS_MODULE_INFO_1); + + LOG_INFO(L"(%s) Libs import:%llu export:%llu", filename.data(), dynInfo->importedLibs.size(), dynInfo->exportedLibs.size()); + + LOG_TRACE(L"<-- parseDynamicInfo %s", filename.data()); + return dynInfo; +} + +void Parser_ELF64::setupMissingRelocationHandler(Program* prog, void* relocateHandler, void* payload) { + uint64_t const pltgot_vaddr = m_dynamicInfo->vaddrPltgot + prog->baseVaddr; + uint64_t const pltgot_size = static_cast(3) * 8; + void const** pltgot = reinterpret_cast(pltgot_vaddr); + + int oldMode {}; + memory::protect(pltgot_vaddr, pltgot_size, SceProtWrite, &oldMode); + + pltgot[1] = payload; + pltgot[2] = relocateHandler; + + memory::protect(pltgot_vaddr, pltgot_size, oldMode); + + if (memory::isExecute(oldMode)) { + flushInstructionCache(pltgot_vaddr, pltgot_size); + } + + if (prog->pltVaddr == 0) { + prog->pltNum = m_dynamicInfo->relaTableTotalSize / sizeof(elf64::Elf64_Rela); + auto size = CallPlt::getSize(prog->pltNum); + prog->pltVaddr = memory::alloc(SYSTEM_RESERVED, size, SceProtWrite); + + auto* code = new (reinterpret_cast(prog->pltVaddr)) CallPlt(prog->pltNum); + code->setPltGot(pltgot_vaddr); + memory::protect(prog->pltVaddr, size, SceProtExecute); + flushInstructionCache(prog->pltVaddr, size); + } +} + +void Parser_ELF64::relocate(Program const* prog, uint64_t invalidMemoryAddr, std::string_view libName) { + LOG_USE_MODULE(ELF64); + LOG_INFO(L"relocate %s", prog->filename.c_str()); + + auto load = [this](uint64_t const offset) -> uint64_t { + uint64_t ret = 0; + auto const* src = m_relro + (offset - m_relroVaddr); + memcpy((uint8_t*)&ret, src, sizeof(ret)); + return ret; + }; + + auto relocateAll = [&](IRuntimeLinker& linker, elf64::Elf64_Rela const* records, uint64_t numBytes, bool const isJmpRelaTable) { + uint32_t index = 0; + for (auto const* r = records; reinterpret_cast(r) < reinterpret_cast(records) + numBytes; r++, index++) { + auto const ri = getRelocationInfo(&linker, r, prog, m_dynamicInfo->symbolsMap); + + if (!libName.empty()) { + auto idData = util::splitString(ri.name, '#'); + + if (idData.size() == 3) { + auto lib = getLibrary(m_dynamicInfo.get(), idData[1]); + if (lib->name != libName) continue; + } else + continue; + } + + bool patched = false; + if (ri.resolved) { + patched = patchReplace(ri.vaddr, ri.value); + } else { + uint64_t value = 0; + bool const weak = (ri.bind == Symbols::BindType::Weak || !prog->failGlobalUnresolved); + + if (ri.type == Symbols::SymbolType::Object && weak) { + value = invalidMemoryAddr; + } else if (ri.type == Symbols::SymbolType::Func && isJmpRelaTable) { + if (prog->pltVaddr != 0) { + value = reinterpret_cast(prog->pltVaddr)->getAddr(index); + } else { + value = load(ri.vaddr) + ri.base_vaddr; + } + } else if ((ri.type == Symbols::SymbolType::Func && !isJmpRelaTable) || (ri.type == Symbols::SymbolType::NoType && weak)) { + value = load(ri.vaddr) + ri.base_vaddr; + } + + if (value != 0) { + patched = patchReplace(ri.vaddr, value); + } else { + LOG_USE_MODULE(ELF64); + LOG_CRIT(L"Can't resolve(%s): [0x%08llx] <- 0x%08llx %S, %S, %S, rela:%d weak:%d test:0x%08llx", prog->filename.c_str(), ri.vaddr, ri.value, + ri.name.data(), magic_enum::enum_name(ri.type).data(), magic_enum::enum_name(ri.bind).data(), isJmpRelaTable, weak, load(ri.vaddr)); + } + } + } + }; + + auto& linker = accessRuntimeLinker(); + relocateAll(linker, m_dynamicInfo->relaTable, m_dynamicInfo->relaTableTotalSize, false); + relocateAll(linker, m_dynamicInfo->jmprelaTable, m_dynamicInfo->jmprelaTableSize, true); +} + +std::unique_ptr buildParser_Elf64(std::filesystem::path&& path, std::unique_ptr&& file) { + auto inst = std::make_unique(std::move(path), std::move(file)); + + return std::unique_ptr(inst.release()); +} + +void Parser_ELF64::dump(std::fstream& outFile, std::fstream& mapFile) { + LOG_USE_MODULE(ELF64); + + // Copy elf part + m_file->seekg(m_addrElf); + outFile << m_file->rdbuf(); + // - + + for (size_t i = 0; i < m_customHeader->numSegments; ++i) { + auto const& seg = m_segmentHeader[i]; + if ((seg.type & 0x800u) != 0) { + auto const index = ((seg.type >> 20u) & 0xFFFu); + auto const& pHeaderCur = m_progHeader[index]; + if (pHeaderCur.type == elf64::PH_LOAD || pHeaderCur.type == elf64::PH_OS_RELRO) { + m_file->seekg(seg.offset); + outFile.seekg(pHeaderCur.offset); + std::vector buffer(seg.sizeDecom); + m_file->read(buffer.data(), buffer.size()); + outFile.write(buffer.data(), buffer.size()); + + LOG_INFO(L"copy [%llu %llu] @0x%08llx| 0x%08llx-> 0x%08llx", i, index, pHeaderCur.offset, seg.offset, pHeaderCur.offset); + } + } + } + + // Map file + if (!m_dynamicInfo) m_dynamicInfo = parseDynamicInfo(m_filename.c_str(), 0); + std::string mappings; + auto getSymbols = [&](elf64::Elf64_Rela const* records, uint64_t numBytes) { + for (auto const* r = records; reinterpret_cast(r) < reinterpret_cast(records) + numBytes; r++) { + auto& sym = m_dynamicInfo->symbolsMap.getSymbol(r->getSymbol()); + auto name = m_dynamicInfo->symbolsMap.getName(sym); + + if (!name.empty()) { + auto idData = util::splitString(name, '#'); + mappings += std::format("{} {:#x}\n", std::string(idData[0].data(), idData[0].size()).data(), r->offset); + } + } + }; + + getSymbols(m_dynamicInfo->relaTable, m_dynamicInfo->relaTableTotalSize); + getSymbols(m_dynamicInfo->jmprelaTable, m_dynamicInfo->jmprelaTableSize); + + mapFile.write(mappings.data(), mappings.size()); +} + +void elf64::dump(IFormat* format, std::fstream& outFile, std::fstream& mapFile) { + ((Parser_ELF64*)format)->dump(outFile, mapFile); +} diff --git a/core/runtime/formats/elf64.h b/core/runtime/formats/elf64.h new file mode 100644 index 00000000..b9fb7eff --- /dev/null +++ b/core/runtime/formats/elf64.h @@ -0,0 +1,295 @@ +#pragma once + +#include "IFormat.h" + +#include +#include +#include +#include + +namespace elf64 { + +#pragma pack(push, 1) // Structs match file data + +// ### Custom Structs +struct CustomHeader { + uint8_t ident[12]; + uint16_t size1; + uint16_t size2; + uint64_t sizeFile; + uint16_t numSegments; + uint16_t unknown; + uint32_t pad; +}; + +struct SegmentHeader { + uint64_t type; + uint64_t offset; + uint64_t sizeCom; /// compressed + uint64_t sizeDecom; /// decompressed +}; + +constexpr unsigned char EI_ABIVERSION = 8; + +constexpr uint64_t ET_EXEC = 0xfe00; /// Exe +constexpr uint64_t ET_DYNEXEC = 0xfe10; /// Exe +constexpr uint64_t ET_DYNAMIC = 0xfe18; /// Lib +constexpr uint64_t EM_ARCH_X86_64 = 0x3e; + +// ### Program Segment type +constexpr uint64_t PH_LOAD = 1; +constexpr uint64_t PH_DYNAMIC = 2; +constexpr uint64_t PH_TLS = 7; +constexpr uint64_t PH_OS_DYNLIBDATA = 0x61000000; +constexpr uint64_t PH_OS_PROCPARAM = 0x61000001; +constexpr uint64_t PH_OS_MODULEPARAM = 0x61000002; +constexpr uint64_t PH_OS_RELRO = 0x61000010; +constexpr uint64_t PH_GNU_EH_HEADER = 0x6474e550; + +constexpr uint64_t SF_ORDR = 0x1; // ordered? +constexpr uint64_t SF_ENCR = 0x2; // encrypted +constexpr uint64_t SF_SIGN = 0x4; // signed +constexpr uint64_t SF_DFLG = 0x8; // deflated +constexpr uint64_t SF_BFLG = 0x800; // block segment + +constexpr int64_t DT_DEBUG = 0x00000015; +constexpr int64_t DT_FINI = 0x0000000d; +constexpr int64_t DT_FINI_ARRAY = 0x0000001a; +constexpr int64_t DT_FINI_ARRAYSZ = 0x0000001c; +constexpr int64_t DT_FLAGS = 0x0000001e; +constexpr int64_t DT_INIT = 0x0000000c; +constexpr int64_t DT_INIT_ARRAY = 0x00000019; +constexpr int64_t DT_INIT_ARRAYSZ = 0x0000001b; +constexpr int64_t DT_NEEDED = 0x00000001; +constexpr int64_t DT_OS_EXPORT_LIB = 0x61000013; +constexpr int64_t DT_OS_EXPORT_LIB_1 = 0x61000047; +constexpr int64_t DT_OS_EXPORT_LIB_ATTR = 0x61000017; +constexpr int64_t DT_OS_FINGERPRINT = 0x61000007; +constexpr int64_t DT_OS_HASH = 0x61000025; +constexpr int64_t DT_OS_HASHSZ = 0x6100003d; +constexpr int64_t DT_OS_IMPORT_LIB = 0x61000015; +constexpr int64_t DT_OS_IMPORT_LIB_1 = 0x61000049; +constexpr int64_t DT_OS_IMPORT_LIB_ATTR = 0x61000019; +constexpr int64_t DT_OS_JMPREL = 0x61000029; +constexpr int64_t DT_OS_MODULE_ATTR = 0x61000011; +constexpr int64_t DT_OS_MODULE_INFO = 0x6100000d; +constexpr int64_t DT_OS_MODULE_INFO_1 = 0x61000043; +constexpr int64_t DT_OS_NEEDED_MODULE = 0x6100000f; +constexpr int64_t DT_OS_NEEDED_MODULE_1 = 0x61000045; +constexpr int64_t DT_OS_ORIGINAL_FILENAME = 0x61000009; +constexpr int64_t DT_OS_ORIGINAL_FILENAME_1 = 0x61000041; +constexpr int64_t DT_OS_PLTGOT = 0x61000027; +constexpr int64_t DT_OS_PLTREL = 0x6100002b; +constexpr int64_t DT_OS_PLTRELSZ = 0x6100002d; +constexpr int64_t DT_OS_RELA = 0x6100002f; +constexpr int64_t DT_OS_RELAENT = 0x61000033; +constexpr int64_t DT_OS_RELASZ = 0x61000031; +constexpr int64_t DT_OS_STRSZ = 0x61000037; +constexpr int64_t DT_OS_STRTAB = 0x61000035; +constexpr int64_t DT_OS_SYMENT = 0x6100003b; +constexpr int64_t DT_OS_SYMTAB = 0x61000039; +constexpr int64_t DT_OS_SYMTABSZ = 0x6100003f; +constexpr int64_t DT_PREINIT_ARRAY = 0x00000020; +constexpr int64_t DT_PREINIT_ARRAYSZ = 0x00000021; +constexpr int64_t DT_REL = 0x00000011; +constexpr int64_t DT_RELA = 0x00000007; +constexpr int64_t DT_SONAME = 0x0000000e; +constexpr int64_t DT_TEXTREL = 0x00000016; + +constexpr int64_t DT_HASH = 0x00000004; +constexpr int64_t DT_STRTAB = 0x00000005; +constexpr int64_t DT_STRSZ = 0x0000000a; +constexpr int64_t DT_SYMTAB = 0x00000006; +constexpr int64_t DT_SYMENT = 0x0000000b; +constexpr int64_t DT_PLTGOT = 0x00000003; +constexpr int64_t DT_PLTREL = 0x00000014; +constexpr int64_t DT_JMPREL = 0x00000017; +constexpr int64_t DT_PLTRELSZ = 0x00000002; +constexpr int64_t DT_RELASZ = 0x00000008; +constexpr int64_t DT_RELAENT = 0x00000009; +constexpr int64_t DT_RELACOUNT = 0x6ffffff9; + +constexpr uint8_t STB_LOCAL = 0; +constexpr uint8_t STB_GLOBAL = 1; +constexpr uint8_t STB_WEAK = 2; + +constexpr uint8_t STT_NOTYPE = 0; +constexpr uint8_t STT_OBJECT = 1; +constexpr uint8_t STT_FUNC = 2; + +constexpr uint32_t R_X86_64_64 = 1; +constexpr uint32_t R_X86_64_GLOB_DAT = 6; +constexpr uint32_t R_X86_64_JUMP_SLOT = 7; +constexpr uint32_t R_X86_64_RELATIVE = 8; +constexpr uint32_t R_X86_64_DTPMOD64 = 16; +constexpr uint32_t R_X86_64_TPOFF64 = 18; + +// DWARF +constexpr uint8_t DW_EH_PE_omit = 0xff; +constexpr uint8_t DW_EH_PE_uleb128 = 0x01; +constexpr uint8_t DW_EH_PE_udata2 = 0x02; +constexpr uint8_t DW_EH_PE_udata4 = 0x03; +constexpr uint8_t DW_EH_PE_udata8 = 0x04; +constexpr uint8_t DW_EH_PE_sleb128 = 0x09; +constexpr uint8_t DW_EH_PE_sdata2 = 0x0A; +constexpr uint8_t DW_EH_PE_sdata4 = 0x0B; +constexpr uint8_t DW_EH_PE_sdata8 = 0x0C; + +constexpr uint8_t DW_EH_PE_absptr = 0x00; +constexpr uint8_t DW_EH_PE_pcrel = 0x10; +constexpr uint8_t DW_EH_PE_datarel = 0x30; + +// ### ELF Structs + +struct ElfHeader { + unsigned char ident[16]; + uint16_t type; + uint16_t machine; + uint32_t version; + uint64_t entry; + uint64_t phOff; + uint64_t shOff; + uint32_t flags; + uint16_t sizeEh; // Elf header size + uint16_t sizePh; /// Program segment Header size + uint16_t numPh; + uint16_t sizeSh; /// Section Header size + uint16_t numSh; + uint16_t indexSh; +}; + +struct ProgramHeader { + uint32_t type; + uint32_t flags; + uint64_t offset; + uint64_t vaddr; + uint64_t paddr; + uint64_t sizeFile; + uint64_t sizeMem; + uint64_t align; +}; + +struct SectionHeader { + uint32_t name; + uint32_t type; + uint64_t flags; + uint64_t addr; + uint64_t offset; + uint64_t size; + uint32_t link; + uint32_t info; + uint64_t addrAlign; + uint64_t entrySize; +}; + +struct DynamicHeader { + uint64_t tag; + + union { + uint64_t value; + uint64_t ptr; + } _un; +}; + +struct Elf64_Sym { + unsigned char getBind() const { return info >> 4u; } + + unsigned char getType() const { return info & 0xfu; } + + uint32_t name; + unsigned char info; + unsigned char other; + uint16_t shndx; + uint64_t value; + uint64_t size; +}; + +struct Elf64_Rela { + auto getSymbol() const { return (uint32_t)(info >> 32u); } + + auto getType() const { return (uint32_t)(info & 0xffffffff); } + + uint64_t offset; + uint64_t info; + int64_t addend; +}; + +#pragma pack(pop) + +struct ModuleId { + bool operator==(const ModuleId& other) const { return versionMajor == other.versionMajor && versionMinor == other.versionMinor && name == other.name; } + + std::string id; + int versionMajor; + int versionMinor; + std::string_view name; +}; + +class SymbolMap { + uint32_t const* const m_hashTable; + uint64_t const m_hashTableSize; + char const* const m_strTable; + uint64_t const m_strTableSize; + Elf64_Sym const* m_symbolTable; + uint64_t const m_symbolTableSizeTotal; + + public: + SymbolMap(uint32_t const* hashTable, uint64_t hashTableSize, char const* strTable, uint64_t strTableSize, Elf64_Sym const* symbolTable, + uint64_t symbolTableSizeTotal) + : m_hashTable(hashTable), + m_hashTableSize(hashTableSize), + m_strTable(strTable), + m_strTableSize(strTableSize), + m_symbolTable(symbolTable), + m_symbolTableSizeTotal(symbolTableSizeTotal) {} + + size_t size() const { return m_symbolTableSizeTotal; } + + // e.g. symname = "LHuSmO3SLd8#libc#libc" + Elf64_Sym const* find(std::string_view symname) const; + + // eg "Qoo175Ig+-k", "libc", "libc" + elf64::Elf64_Sym const* find(std::string_view symname, std::string_view libName, std::string_view modName) const; + + Elf64_Sym const& getSymbol(size_t index) const { return m_symbolTable[index]; }; + + std::string_view getName(Elf64_Sym const& symbol) const { return m_strTable + symbol.name; }; +}; + +struct DynamicInfo { + SymbolMap const symbolsMap; + + DynamicInfo(SymbolMap&& symbolsMap): symbolsMap(std::move(symbolsMap)) {} + + std::optional vaddrInit; + std::optional vaddrFini = 0; + std::optional vaddrInitArray = 0; + std::optional vaddrFinArray = 0; + std::optional vaddrPreinitArray = 0; + uint64_t vaddrPltgot = 0; + + uint64_t sizeInitArray = 0; + uint64_t sizeFiniArray = 0; + uint64_t sizePreinitArray = 0; + + Elf64_Rela const* jmprelaTable = nullptr; + uint64_t jmprelaTableSize = 0; + + Elf64_Rela const* relaTable = nullptr; + uint64_t relaTableTotalSize = 0; + uint64_t relaTableEntrySize = 0; + + uint64_t relativeCount = 0; + + uint64_t debug = 0; + uint64_t textrel = 0; + uint64_t flags = 0; + + std::unordered_map exportedLibs; + std::unordered_map exportedMods; + std::unordered_map importedLibs; + std::unordered_map importedMods; +}; + +void dump(IFormat* format, std::fstream& outFile, std::fstream& mapFile); +} // namespace elf64 \ No newline at end of file diff --git a/core/runtime/memoryLayout.h b/core/runtime/memoryLayout.h new file mode 100644 index 00000000..1a27123c --- /dev/null +++ b/core/runtime/memoryLayout.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +constexpr uint64_t SYSTEM_RESERVED = 0x800000000u; +constexpr uint64_t INVALID_OFFSET = 0x040000000u; + +constexpr uint64_t INVALID_MEMORY = SYSTEM_RESERVED + INVALID_OFFSET; + +constexpr size_t XSAVE_BUFFER_SIZE = 2688; diff --git a/core/runtime/procParam.h b/core/runtime/procParam.h new file mode 100644 index 00000000..62b1f5fe --- /dev/null +++ b/core/runtime/procParam.h @@ -0,0 +1,112 @@ +#pragma once +#include + +struct SceKernelMemParam { + uint64_t* sceKernelExtendedPageTable; + uint64_t* sceKernelFlexibleMemorySize; + uint8_t* sceKernelExtendedMemory1; + uint64_t const* sceKernelExtendedGpuPageTable; + uint8_t const* sceKernelExtendedMemory2; + uint64_t const* sceKernelExtendedCpuPageTable; +}; + +struct SceKernelFsParam { + uint64_t size; + uint64_t* sceKernelFsDupDent; + void* sceWorkspaceUpdateMode; + void* sceTraceAprNameMode; +}; + +struct SceMallocReplace { + uint64_t size = sizeof(SceMallocReplace); + uint64_t unknown1 = 1; + void* malloc_init = nullptr; + void* malloc_finalize = nullptr; + void* malloc = nullptr; + void* free = nullptr; + void* calloc = nullptr; + void* realloc = nullptr; + void* memalign = nullptr; + void* reallocalign = nullptr; + void* posix_memalign = nullptr; + void* malloc_stats = nullptr; + void* malloc_stats_fast = nullptr; + void* malloc_usable_size = nullptr; +}; + +struct SceLibcNewReplace { + uint64_t Size = sizeof(SceLibcNewReplace); + uint64_t Unknown1 = 0x2; + void* user_new = nullptr; + void* user_new_nothrow = nullptr; + void* user_new_array = nullptr; + void* user_new_array_nothrow = nullptr; + void* user_delete = nullptr; + void* user_delete_nothrow = nullptr; + void* user_delete_array = nullptr; + void* user_delete_array_nothrow = nullptr; + // optional + // void* user_delete_3= nullptr; + // void* user_delete_4= nullptr; + // void* user_delete_array_3= nullptr; + // void* user_delete_array_4= nullptr; +}; + +struct SceLibcParam0 { + uint64_t Size; + uint32_t entry_count; +}; + +struct SceLibcParam1: SceLibcParam0 { + uint32_t SceLibcInternalHeap; //(entry_count > 1) + uint32_t* sceLibcHeapSize; + uint32_t* sceLibcHeapDelayedAlloc; + uint32_t* sceLibcHeapExtendedAlloc; + uint32_t* sceLibcHeapInitialSize; + SceMallocReplace* _sceLibcMallocReplace; + SceLibcNewReplace* _sceLibcNewReplace; +}; + +struct SceLibcParam2: SceLibcParam1 { + uint64_t* sceLibcHeapHighAddressAlloc; //(entry_count > 2) + uint32_t* Need_sceLibc; +}; + +struct SceLibcParam3: SceLibcParam2 { + uint64_t* sceLibcHeapMemoryLock; //(entry_count > 4) + uint64_t* sceKernelInternalMemorySize; + uint64_t* _sceLibcMallocReplaceForTls; +}; + +struct SceLibcParam4: SceLibcParam3 { + uint64_t* sceLibcMaxSystemSize; //(entry_count > 7) +}; + +struct SceLibcParam5: SceLibcParam4 { + uint64_t* sceLibcHeapDebugFlags; //(entry_count > 8) + uint32_t* sceLibcStdThreadStackSize; + void* Unknown3; + uint32_t* sceKernelInternalMemoryDebugFlags; + uint32_t* sceLibcWorkerThreadNum; + uint32_t* sceLibcWorkerThreadPriority; + uint32_t* sceLibcThreadUnnamedObjects; +}; + +struct ProcParam { + struct { + uint64_t size; /// 0x50 + uint32_t magic; /// "orbi" (0x4942524F) + uint32_t entryCount; /// >=1 + uint64_t sdkVersion; /// 0x4508101 + } header; + + char const* sceProcessName; + char const* sceUserMainThreadName; + uint32_t const* sceUserMainThreadPriority; + uint32_t const* sceUserMainThreadStackSize; + SceLibcParam0* PSceLibcParam; + SceKernelMemParam* _sceKernelMemParam; + SceKernelFsParam* _sceKernelFsParam; + uint32_t* sceProcessPreloadEnabled; + uint64_t* unknown; +}; \ No newline at end of file diff --git a/core/runtime/program.h b/core/runtime/program.h new file mode 100644 index 00000000..3c024be4 --- /dev/null +++ b/core/runtime/program.h @@ -0,0 +1,73 @@ +#pragma once + +#include "runtimeExport.h" + +#include +#include +#include +#include +#include + +struct ThreadLocalStorage { + uint32_t index; + uintptr_t vaddrImage = 0; + uint64_t sizeImage = 0; + uint64_t offset = 0; + + uint32_t alignment = 0; +}; + +struct Program; +class IFormat; + +struct RelocateHandlerPayload { + Program* prog; + IFormat* format; +}; + +struct Program { + RelocateHandlerPayload relocatePayload; + + uint64_t baseVaddr = 0; + uint64_t entryOffAddr = 0; + + bool started = false; + + bool failGlobalUnresolved = true; + uint64_t procParamVaddr = 0; + + ThreadLocalStorage tls; + + SceKernelModuleInfoEx moduleInfoEx; + + uint64_t pltVaddr = 0; + uint32_t pltNum = 0; + bool isMainProg = false; + + std::filesystem::path const filename; + std::filesystem::path const path; + + int32_t id; + uint64_t const baseSize; + uint64_t const baseSizeAligned; + uint64_t const desiredBaseAddr; + uint64_t const allocSize; + + bool const useStaticTLS; + + std::vector trampolines_fs; + + std::vector cxa; + + Program(std::filesystem::path path_, uint64_t baseSize_, uint64_t baseSizeAligned_, uint64_t desiredBaseAddr_, uint64_t allocSize_, bool useStaticTLS_) + : filename(path_.filename()), + path(path_), + id(-1), + baseSize(baseSize_), + baseSizeAligned(baseSizeAligned_), + desiredBaseAddr(desiredBaseAddr_), + allocSize(allocSize_), + useStaticTLS(useStaticTLS_) {} + + ~Program() {} +}; diff --git a/core/runtime/runtimeExport.h b/core/runtime/runtimeExport.h new file mode 100644 index 00000000..925b8e98 --- /dev/null +++ b/core/runtime/runtimeExport.h @@ -0,0 +1,146 @@ +#pragma once + +#include "utility/utility.h" + +#include +#include + +using cxa_destructor_func_t = void (*)(void*); + +struct CxaDestructor { + cxa_destructor_func_t destructor_func; + void* destructor_object; +}; + +#pragma pack(push, 1) + +struct alignas(32) EntryParams { + int argc = 0; + uint32_t pad = 0; + const char* argv[3] = {0, 0, 0}; +}; + +struct ModulInfo { + uint64_t seg0Addr; + uint64_t seg0Size; + uint64_t procParamAddr; +}; + +struct SceKernelModuleSegmentInfo { + uint64_t address; + uint32_t size; + int prot; +}; + +struct SceKernelModuleInfo { + uint64_t size = sizeof(SceKernelModuleInfo); + char name[255]; + SceKernelModuleSegmentInfo segments[3]; + uint32_t segment_count; + uint32_t ref_count; + uint8_t fingerprint[20]; +}; + +struct SceKernelModuleInfoEx { + uint64_t size = sizeof(SceKernelModuleInfoEx); + char name[255]; + int id; + uint32_t tls_index; + void* tls_init_addr; + uint32_t tls_init_size; + uint32_t tls_size; + uint32_t tls_offset; + uint32_t tls_align; + uint64_t init_proc_addr; + uint64_t fini_proc_addr; + uint64_t reserved1 = 0; + uint64_t reserved2 = 0; + void* eh_frame_hdr_addr; + void* eh_frame_addr; + uint32_t eh_frame_hdr_size; + uint32_t eh_frame_size; + + SceKernelModuleSegmentInfo segments[3]; + uint32_t segment_count; + uint32_t ref_count; +}; + +#pragma pack(pop) + +class IRuntimeExport { + CLASS_NO_COPY(IRuntimeExport); + CLASS_NO_MOVE(IRuntimeExport); + + protected: + IRuntimeExport() = default; + + public: + virtual ~IRuntimeExport() = default; + + virtual int loadStartModule(std::filesystem::path const& path, size_t args, const void* argp, int* pRes) = 0; + + virtual void initTLS(uint8_t* obj) = 0; + + virtual uint64_t getTLSStaticBlockSize() const = 0; + + virtual EntryParams const* getEntryParams() const = 0; + + virtual void cxa_add_atexit(CxaDestructor&&, int moduleId) = 0; + virtual void cxa_finalize(int moduleId) = 0; + + virtual ModulInfo mainModuleInfo() const = 0; + + virtual SceKernelModuleInfoEx const* getModuleInfoEx(uint64_t vaddr) const = 0; + + virtual std::vector getModules() const = 0; + + /** + * @brief Get the symbols address + * + * @param symName nid + * @param libName + * @param modName + * @return void* address + */ + virtual void* getSymbol(std::string_view symName, std::string_view libName, std::string_view modName) const = 0; + + /** + * @brief Searches the module for the symbol + * + * @param module moduleId + * @param symName + * @param isNid + * @return void* + */ + virtual void* getSymbol(int module, std::string_view symName, bool isNid) const = 0; + + // ### THREAD LOCAL STORAGE + virtual void* getTLSAddr(uint32_t index, uint64_t offset) = 0; + virtual void setTLSKey(uint32_t key, const void* value) = 0; + virtual void* getTLSKey(uint32_t key) = 0; + virtual uint32_t createTLSKey(void* destructor) = 0; + virtual void deleteTLSKey(uint32_t key) = 0; + virtual void destroyTLSKeys(uint8_t* obj) = 0; + // - ### TLS + + // ### Interception + + /** + * @brief function (addr) is called instead of the library symbol + * + * @param addr function address + * @param name symbol name (nid) + * @param libraryName + * @param modulName + */ + virtual void interceptAdd(uintptr_t addr, std::string_view name, std::string_view libraryName, std::string_view modulName) = 0; + + /** + * @brief Gets the original addr + * + * @param addr + * @return uintptr_t + */ + virtual uintptr_t interceptGetAddr(uintptr_t addr) const = 0; + // - +}; diff --git a/core/runtime/runtimeLinker.cpp b/core/runtime/runtimeLinker.cpp new file mode 100644 index 00000000..4514021b --- /dev/null +++ b/core/runtime/runtimeLinker.cpp @@ -0,0 +1,922 @@ +#define __APICALL_EXTERN +#include "runtimeLinker.h" +#undef __APICALL_EXTERN + +#include "core/fileManager/fileManager.h" +#include "core/fileManager/types/type_lib.h" +#include "core/kernel/pthread.h" +#include "core/kernel/pthread_intern.h" +#include "core/memory/memory.h" +#include "formats/elf64.h" +#include "logging.h" +#include "memoryLayout.h" +#include "util/moduleLoader.h" +#include "util/plt.h" +#include "util/virtualmemory.h" +#include "utility/progloc.h" +#include "utility/utility.h" + +#include +#include +#include +#include +#include +#include + +LOG_DEFINE_MODULE(RuntimeLinker); + +namespace { +// clang-format off + +using atexit_func_t = SYSV_ABI void (*)(); +using entry_func_t = SYSV_ABI void (*)(EntryParams const* params, atexit_func_t atexit_func); +using module_func_t = SYSV_ABI int (*)(size_t args, const void* argp, atexit_func_t atexit_func); + +// clang-format on + +struct FrameS { + FrameS* next; + uintptr_t ret_addr; +}; + +static Program* g_tlsMainProgram = nullptr; + +struct RelocateHandlerStack { + uint64_t stack[3]; +}; + +SYSV_ABI void missingRelocationHandler(RelocateHandlerStack s) { + LOG_USE_MODULE(RuntimeLinker); + + auto* stack = s.stack; + auto const* payload = reinterpret_cast(stack[-1]); + auto const relIndex = stack[0]; + + Symbols::SymbolInfo symInfo = {.name = ""}; + + if (payload != nullptr) { + symInfo = payload->format->getSymbolInfo(relIndex); + } + + LOG_CRIT(L"Missing Symbol| Name:%S Lib:%S(ver:%d) Mod:%S(major:%d minor:%d)", symInfo.name.data(), symInfo.libraryName.data(), symInfo.libraryVersion, + symInfo.modulName.data(), symInfo.moduleVersionMajor, symInfo.moduleVersionMinor); +} + +SYSV_ABI void tlsDynDestruct(void* tlsBlock) { + if (tlsBlock != nullptr) delete[] (uint8_t*)tlsBlock; +} + +void patchCall(uint64_t const srcFunction, uint64_t const dstFunction) { + int oldMode; + memory::protect(srcFunction - 1, 20, SceProtRead | SceProtWrite, + &oldMode); // Protect Memory + + uint8_t* ptr = (uint8_t*)srcFunction; + + ptr[0] = 0xFF; + ptr[1] = 0x25; + ptr[2] = 0x00; + ptr[3] = 0x00; + ptr[4] = 0x00; + ptr[5] = 0x00; + + *((uint64_t*)&ptr[6]) = dstFunction; + // memset(ptr + 5, 0x90, 4); + memory::protect(srcFunction - 1, 20, oldMode); // Protect Memory + flushInstructionCache(srcFunction, 20); +} + +std::string encode(const uint8_t* in) { + std::string out; + out.resize(12); + + auto pOut = out.data(); + + constexpr const char* tab = {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"}; + + for (auto n = 8 / 3; n--;) { + *pOut++ = tab[(in[0] & 0xfc) >> 2]; + *pOut++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *pOut++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)]; + *pOut++ = tab[in[2] & 0x3f]; + in += 3; + } + + *pOut++ = tab[(in[0] & 0xfc) >> 2]; + *pOut++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)]; + *pOut++ = tab[(in[1] & 0x0f) << 2]; + + *pOut = '\0'; + return out; +} + +std::string name2nid(std::string_view name) { + boost::uuids::detail::sha1 sha1; + sha1.process_bytes((unsigned char*)name.data(), name.size()); + sha1.process_bytes("\x51\x8d\x64\xa6\x35\xde\xd8\xc1\xE6\xB0\x39\xB1\xC3\xE5\x52\x30", 16); + + unsigned hash[5]; + sha1.get_digest(hash); + + uint64_t const digest = ((uint64_t)hash[0] << 32u) | hash[1]; + return encode((uint8_t*)&digest); +} +} // namespace + +class InternalLib: public Symbols::IResolve { + std::unordered_map const m_exportedLibs; + + std::unordered_map const m_importedLibs {}; /// dummy + std::unordered_map> m_symExport; /// [Module name] symbolsExport + + public: + InternalLib(std::string_view const modulName, std::shared_ptr& symExport) + : m_exportedLibs({std::make_pair(symExport->libraryName, LibraryId {})}) { + m_symExport.insert({modulName, symExport}); + } + + void addModule(std::shared_ptr& symExport) { + if (auto it = m_symExport.find(symExport->modulName); it != m_symExport.end()) { + // add to existing + + if (it->second->symbolsMap.size() > symExport->symbolsMap.size()) { + it->second->symbolsMap.insert(symExport->symbolsMap.begin(), symExport->symbolsMap.end()); + } else { + // add to biggest and swap (owns symExport) + symExport->symbolsMap.insert(it->second->symbolsMap.begin(), it->second->symbolsMap.end()); + it->second->symbolsMap.swap(symExport->symbolsMap); + } + } else { + // inster new + m_symExport.insert({symExport->modulName, std::move(symExport)}); + } + } + + std::unordered_map const& getExportedLibs() const final { return m_exportedLibs; } + + std::unordered_map const& getImportedLibs() const final { return m_importedLibs; } + + std::string_view const getLibName() const { return m_exportedLibs.begin()->first; } + + uintptr_t getAddress(std::string_view symName, std::string_view libName, std::string_view modName) const final { + auto it = m_symExport.find(modName); + if (it == m_symExport.end()) return 0; + + auto itSym = it->second->symbolsMap.find(std::string(symName)); + if (itSym == it->second->symbolsMap.end()) { + // Special case: check libScePosix aswell + if (libName == "libkernel" && modName == "libkernel") { + auto iResolve = accessRuntimeLinker().getIResolve("libScePosix"); + if (iResolve != nullptr) return iResolve->getAddress(symName, "libScePosix", modName); + } + // - + return 0; + } + + return itSym->second.vaddr; + } + + uintptr_t getAddress(std::string_view symName) const final { + for (auto const& mod: m_symExport) { + auto itSym = mod.second->symbolsMap.find(std::string(symName)); + return itSym->second.vaddr; + } + + return 0; + } +}; + +class RuntimeLinker: public IRuntimeLinker { + private: + mutable std::recursive_mutex m_mutex_int; + + uint64_t m_invalidMemoryAddr = 0; + size_t m_countcreatePrograms = 0; + + EntryParams m_entryParams = { + .argc = 1, + .argv = {"psOff", "", ""}, + }; + + /** + * @brief Programlist ( 0: mainProgram) + * + */ + std::list, std::shared_ptr>> m_programList; + std::unordered_map> m_internalLibs; + std::unordered_map> m_libsMap; + std::unordered_map m_interceptMap; + mutable std::unordered_map m_interceptOrigVaddrMap; + + std::unordered_map mTlsIndexMap; + std::unordered_map m_libHandles; + + std::vector m_tlsStaticInitBlock; + std::unordered_map m_threadList; + + uint64_t m_curDTVVersion = 1; + + std::array m_dtvKeys; + + bool m_checkOldLib = true; /// Skip check for oldLib in addExport if already found + + void initTlsStaticBlock(); + void setupTlsStaticBlock(); + + void loadModules(std::string_view libName); + + public: + RuntimeLinker() { m_programList.push_back({}); } + + virtual ~RuntimeLinker() = default; + + // Program* := access to unique_ptr + Program* findProgram(uint64_t vaddr) final; + Program* findProgramById(size_t id) const final; + + void callInitProgramms() final; + void* getTLSAddr(uint32_t index, uint64_t offset) final; + + void setTLSKey(uint32_t key, const void* value) final; + void* getTLSKey(uint32_t key) final; + uint32_t createTLSKey(void* destructor) final; + void deleteTLSKey(uint32_t key) final; + void destroyTLSKeys(uint8_t* obj) final; + + std::unique_ptr createProgram(std::filesystem::path const filepath, uint64_t const baseSize, uint64_t const baseSizeAligned, uint64_t const alocSize, + bool useStaticTLS) final { + std::unique_lock const lock(m_mutex_int); + if (useStaticTLS) ++m_countcreatePrograms; + auto inst = std::make_unique(filepath, baseSize, baseSizeAligned, IMAGE_BASE, alocSize, useStaticTLS); + inst->id = accessFileManager().addFile(createType_lib(inst.get()), filepath); + return inst; + } + + uint64_t getAddrInvalidMemory() final { + std::unique_lock const lock(m_mutex_int); + return m_invalidMemoryAddr; + } + + Program* addProgram(std::unique_ptr&& prog, std::shared_ptr format) final; + + int loadStartModule(std::filesystem::path const& path, size_t args, const void* argp, int* pRes) final; + + uintptr_t execute() final; + + void stopModules() final; + void stopModule(int moduleId) final; + + Symbols::IResolve const* getIResolve(std::string_view libName) const final { + auto const it = m_libsMap.find(libName); + if (it == m_libsMap.end()) return nullptr; + + return it->second.get(); + } + + uintptr_t checkIntercept(uintptr_t vaddr, std::string_view symName, std::string_view libName, std::string_view modName) const final { + auto it = m_interceptMap.find(symName); + if (it != m_interceptMap.end()) { + m_interceptOrigVaddrMap[it->second.vaddr] = vaddr; + return it->second.vaddr; + } + return vaddr; + } + + void addExport(std::unique_ptr&& symbols) final { + std::unique_lock const lock(m_mutex_int); + + auto it = m_internalLibs.find(symbols->libraryName); + + std::shared_ptr obj = std::move(symbols); + if (it == m_internalLibs.end()) { + auto inserted = m_internalLibs.insert({obj->libraryName, std::make_shared(obj->modulName, obj)}); + auto const libName = inserted.first->first; + m_libsMap.insert({libName, inserted.first->second}); + + // Check if Old Lib Name + if (m_checkOldLib) { + constexpr std::string_view oldLib = "libSceGnmDriver"; + constexpr std::string_view newLib = "libSceGraphicsDriver"; + if (libName.compare(newLib) == 0) { + m_checkOldLib = false; + m_libsMap.insert({oldLib, std::make_shared(oldLib, obj)}); + } + } + // - + } else { + it->second->addModule(obj); + } + } + + ModulInfo mainModuleInfo() const final { + auto prog = accessMainProg(); + return {prog->moduleInfoEx.segments[0].address, prog->moduleInfoEx.segments[0].size, prog->procParamVaddr}; + } + + SceKernelModuleInfoEx const* getModuleInfoEx(uint64_t vaddr) const final { + std::unique_lock lock(m_mutex_int); + + Program const* prog = nullptr; + for (auto&& p: m_programList) { + if (p.second.get()->containsAddr(vaddr, p.first.get()->baseVaddr)) { + prog = p.first.get(); + break; + } + } + if (prog == nullptr) return nullptr; + + return &prog->moduleInfoEx; + } + + std::vector getModules() const final { + std::unique_lock lock(m_mutex_int); + std::vector ret(m_programList.size()); + + Program const* prog = nullptr; + + auto pDst = ret.data(); + for (auto& prog: m_programList) { + *pDst++ = prog.first->id; + } + return ret; + } + + void* getSymbol(std::string_view symName, std::string_view libName, std::string_view modName) const final { + auto iresolve = getIResolve(libName); + if (iresolve == nullptr) return nullptr; + return (void*)iresolve->getAddress(symName, libName, modName); + } + + void* getSymbol(int moduleId, std::string_view symName, bool isNid) const final { + std::unique_lock lock(m_mutex_int); + + // Find Module + IFormat const* format = nullptr; + for (auto&& prog: m_programList) { + if (prog.first->id == moduleId) { + format = prog.second.get(); + break; + } + } + if (format == nullptr) return nullptr; + // - + + std::string name; + if (isNid) { + name = std::string(symName); + } else { + name = name2nid(symName); + name.pop_back(); + } + + for (auto const& [libName, libId]: format->getExportedLibs()) { + auto iresolve = getIResolve(libName); + if (iresolve == nullptr) continue; + + if (auto const addr = (void*)iresolve->getAddress(name); addr != 0) { + return addr; + } + } + + return nullptr; + } + + void interceptAdd(uintptr_t addr, std::string_view name, std::string_view libraryName, std::string_view modulName) final { + m_interceptMap[name] = Symbols::SymbolIntercept {.vaddr = addr, .name = name, .libraryName = libraryName, .modulName = modulName}; + } + + uintptr_t interceptGetAddr(uintptr_t addr) const final { return m_interceptOrigVaddrMap[addr]; } + + bool interceptInternal(Program* prog, uintptr_t progaddr, uintptr_t iaddr); + + void cxa_add_atexit(CxaDestructor&& dest, int moduleId) final { + std::unique_lock const lock(m_mutex_int); + auto prog = findProgramById(moduleId); + if (prog == nullptr) return; + + prog->cxa.push_back(std::move(dest)); + } + + void cxa_finalize(int moduleId) final { + std::unique_lock const lock(m_mutex_int); + auto prog = findProgramById(moduleId); + if (prog == nullptr) return; + for (auto& c: prog->cxa) { + c.destructor_func(c.destructor_object); + } + prog->cxa.clear(); + } + + void initTLS(uint8_t* tls) final; + + uint64_t getTLSStaticBlockSize() const final { return m_tlsStaticInitBlock.size(); } + + std::vector> getExecSections() const final { return m_programList.begin()->second->getExecSections(); } + + EntryParams const* getEntryParams() const final { return &m_entryParams; }; + + Program* accessMainProg() final { + std::unique_lock const lock(m_mutex_int); + return m_programList.begin()->first.get(); + } + + Program const* accessMainProg() const { + std::unique_lock const lock(m_mutex_int); + return m_programList.begin()->first.get(); + } +}; + +void RuntimeLinker::initTLS(uint8_t* obj) { + LOG_USE_MODULE(RuntimeLinker); + + if (!m_tlsStaticInitBlock.empty()) memcpy(&obj[8], m_tlsStaticInitBlock.data(), m_tlsStaticInitBlock.size()); + auto dtvAddress = pthread::getDTV(obj); + + m_threadList[pthread::getThreadId(obj)] = obj; + + dtvAddress[0] = m_curDTVVersion; + + for (auto const& prog: m_programList) { + if (!prog.first->useStaticTLS) break; // since m_programList is in sequence + + auto& slot = dtvAddress[prog.first->tls.index]; + slot = (uint64_t)&obj[8 + prog.first->tls.offset]; + + LOG_TRACE(L"TLS(%s) slot:%u addr:0x%08llx", prog.first->filename.c_str(), prog.first->tls.index, slot); + } +} + +Program* RuntimeLinker::findProgram(uint64_t vaddr) { + for (auto&& p: m_programList) { + if (p.second.get()->containsAddr(vaddr, p.first.get()->baseVaddr)) { + return p.first.get(); + } + } + return nullptr; +} + +Program* RuntimeLinker::findProgramById(size_t id) const { + for (auto&& p: m_programList) { + if (p.first->id == id) { + return p.first.get(); + } + } + return nullptr; +} + +Program* RuntimeLinker::addProgram(std::unique_ptr&& prog, std::shared_ptr format) { + LOG_USE_MODULE(RuntimeLinker); + LOG_INFO(L"adding %s: main:%S id:%llu", prog->filename.c_str(), util::getBoolStr(prog->isMainProg), prog->id); + + prog->relocatePayload.format = format.get(); + prog->relocatePayload.prog = prog.get(); + format->setupMissingRelocationHandler(prog.get(), reinterpret_cast(missingRelocationHandler), &prog->relocatePayload); + + std::unique_lock const lock(m_mutex_int); + for (auto const& item: format->getExportedLibs()) { + m_libsMap.insert({item.first, format}); + } + + // Place mainprogram at index 0 + if (prog->isMainProg) { + prog->failGlobalUnresolved = false; + g_tlsMainProgram = prog.get(); + + m_programList.begin()->first.swap(prog); + m_programList.begin()->second.swap(format); + return m_programList.begin()->first.get(); + } else { + if (util::endsWith(prog->path.parent_path().string(), "_module")) { + prog->failGlobalUnresolved = false; + } + + return m_programList.emplace_back(std::make_pair(std::move(prog), std::move(format))).first.get(); + } +} + +void RuntimeLinker::loadModules(std::string_view libName) { + LOG_USE_MODULE(RuntimeLinker); + auto funcLoad = [this](std::string_view name) { + LOG_USE_MODULE(RuntimeLinker); + if (!m_libHandles.contains(name)) { + LOG_DEBUG(L"Needs library %S", name.data()); + + // 1/2 Sepcial case: old->new, build filepath + auto filepath = std::format(L"{}/modules/", util::getProgramLoc()); + if (name == "libSceGnmDriver") { + filepath += L"libSceGraphicsDriver"; + } else { + filepath += std::wstring(name.begin(), name.end()); + } + filepath += L".dll"; + //- filepath + + if (std::filesystem::exists(filepath) && !m_libHandles.contains(name)) { + LOG_DEBUG(L" load library %s", filepath.c_str()); + auto [handle, symbols] = loadModule(name.data(), filepath.c_str(), 1); + + // 2/2 Sepcial case: old->new + if (name == "libSceGnmDriver") { + symbols->modulName = name; + } + // -special case + m_libHandles.emplace(std::make_pair(name, handle)); + accessRuntimeLinker().addExport(std::move(symbols)); + } + } + }; + + funcLoad(libName); + // todo include dependency into lib + if (libName == "libSceNpManager") { + funcLoad("libSceNpManagerForToolkit"); + } + // ScePosix is sometimes not loaded together with libkernel -> just allways load it + else if (libName == "libkernel") { + funcLoad("libScePosix"); + } +} + +int RuntimeLinker::loadStartModule(std::filesystem::path const& path, size_t argc, const void* argp, int* pRes) { + LOG_USE_MODULE(RuntimeLinker); + std::unique_lock const lock(m_mutex_int); + + // check if already loaded + auto fileName = path.filename().string(); + if (auto it = std::find_if(m_programList.begin(), m_programList.end(), + [&fileName](std::pair, std::shared_ptr>& rhs) { return rhs.first->filename == fileName; }); + it != m_programList.end()) { + return it->first->id; + } + // - + + auto ifFile = util::openFile(path); + if (!ifFile) { + LOG_ERR(L"Couldn't open file %s", path.c_str()); + return getErr(ErrCode::_ENOENT); + } + + // Load Module + std::shared_ptr format = buildParser_Elf64(std::filesystem::path(path), std::move(ifFile)); + if (!format->init()) { + return 0; + } + + uint64_t baseSize = 0, baseSizeAligned = 0, allocSize = 0; + format->getAllocInfo(baseSize, baseSizeAligned, allocSize); + + auto program = accessRuntimeLinker().createProgram(format->getFilepath(), baseSize, baseSizeAligned, allocSize, false); + + if (!format->load2Mem(program.get())) { + LOG_CRIT(L"<--- Parse (%s): Error:accessRuntimeLinker", format->getFilename().c_str()); + return 0; + } + + auto pProgram = accessRuntimeLinker().addProgram(std::move(program), format); + // - Module loaded + + pProgram->tls.offset = 0; // Dynamically loaded modules use their own blocks + auto const tlsIndex = createTLSKey((void*)tlsDynDestruct); + pProgram->tls.index = tlsIndex; + + pProgram->moduleInfoEx.tls_index = tlsIndex; + + mTlsIndexMap[tlsIndex] = pProgram; + + // check imports + LOG_DEBUG(L"Load for %S", pProgram->filename.string().c_str()); + for (auto const& impLib: format->getImportedLibs()) { + loadModules(impLib.first); + } + // - imports + + // relocate + auto libName = pProgram->filename.stem().string(); + for (auto& prog: m_programList) { + if (prog.second->getImportedLibs().find(libName) == prog.second->getImportedLibs().end()) continue; + + prog.second->relocate(prog.first.get(), m_invalidMemoryAddr, libName); + } + format->relocate(pProgram, m_invalidMemoryAddr, ""); + + uintptr_t const entryAddr = pProgram->entryOffAddr + pProgram->baseVaddr; + LOG_INFO(L"-> Starting %s entry:0x%08llx tlsIndex:%u", pProgram->filename.c_str(), entryAddr, tlsIndex); + int result = ((module_func_t)entryAddr)(argc, argp, nullptr); + if (pRes != nullptr) *pRes = result; + pProgram->started = true; + LOG_INFO(L"<- Started %s entry:0x%08llx", pProgram->filename.c_str(), entryAddr); + // callInitProgramms(); + + return pProgram->id; +} + +/** + * @brief Access Static DTV + * @return void* address + */ +void* tlsMainGetAddr(int32_t offset) { + LOG_USE_MODULE(RuntimeLinker); + uint64_t const tlsAddr = (uint64_t)&pthread::getDTV(pthread::getSelf())[0]; + LOG_TRACE(L"[%d] tlsMainGetAddr addr:0x%08llx offset:0x%0lx", pthread::getThreadId(), tlsAddr, offset); + return &pthread::getDTV(pthread::getSelf())[0] + offset; // dtv +} + +void* tlsMainGetAddr64(int64_t offset) { + LOG_USE_MODULE(RuntimeLinker); + uint64_t const tlsAddr = (uint64_t)&pthread::getDTV(pthread::getSelf())[0]; + LOG_TRACE(L"[%d] tlsMainGetAddr64 addr:0x%08llx offset:0x%08llx", pthread::getThreadId(), tlsAddr, offset); + return &pthread::getDTV(pthread::getSelf())[0] + offset; // dtv +} + +void* RuntimeLinker::getTLSAddr(uint32_t index, uint64_t offset) { + std::unique_lock const lock(m_mutex_int); + + LOG_USE_MODULE(RuntimeLinker); + + if (index <= m_countcreatePrograms) { + auto& tlsAddr = pthread::getDTV(pthread::getSelf())[index]; + LOG_TRACE(L"[%d] getTlsAddr key:%d addr:0x%08llx offset:0x%08llx", pthread::getThreadId(), index, tlsAddr, offset); + return (void*)((uint8_t*)tlsAddr + offset); + } + + // Lookup module and get its tls + auto const prog = mTlsIndexMap[index]; + auto& tlsAddr = pthread::getDTV(pthread::getSelf())[prog->tls.index]; + + if (tlsAddr == 0) { + LOG_TRACE(L"[%d] create tls key:%d", pthread::getThreadId(), index); + auto obj = std::make_unique(prog->tls.sizeImage).release(); + memcpy(obj, (void*)prog->tls.vaddrImage, prog->tls.sizeImage); + tlsAddr = (uint64_t)obj; + } + + LOG_TRACE(L"[%d] getTlsAddr key:%d addr:0x%08llx offset:0x%08llx", pthread::getThreadId(), index, tlsAddr, offset); + return (void*)((uint8_t*)tlsAddr + offset); +} + +void RuntimeLinker::setTLSKey(uint32_t index, const void* value) { + LOG_USE_MODULE(RuntimeLinker); + + std::unique_lock const lock(m_mutex_int); + + auto dtv = pthread::getDTV(pthread::getSelf()); + auto pDtvKey = &dtv[index]; + + LOG_TRACE(L"[thread:%d] TLS Key| -> key:%d value:0x%08llx", pthread::getThreadId(), index, (uint64_t)value); + *pDtvKey = (uint64_t)value; +} + +void* RuntimeLinker::getTLSKey(uint32_t index) { + LOG_USE_MODULE(RuntimeLinker); + + std::unique_lock const lock(m_mutex_int); + + auto dtv = pthread::getDTV(pthread::getSelf()); + auto pDtvKey = &dtv[index]; + + LOG_TRACE(L"[thread:%d] TLS Key| <- key:%d value:0x%08llx", pthread::getThreadId(), index, pDtvKey[index]); + return (void*)*pDtvKey; +} + +uint32_t RuntimeLinker::createTLSKey(void* destructor) { + LOG_USE_MODULE(RuntimeLinker); + + std::unique_lock const lock(m_mutex_int); + + for (uint64_t n = m_countcreatePrograms; n < m_dtvKeys.size(); ++n) { + if (!m_dtvKeys[n].used) { + m_dtvKeys[n].used = true; + m_dtvKeys[n].destructor = (pthread_key_destructor_func_t)destructor; + LOG_TRACE(L"-> TLS Key:%d destructor:0x%08llx", n, reinterpret_cast(destructor)); + return n; + } + } + LOG_ERR(L"Not enough dtv space"); + return -1; +} + +void RuntimeLinker::deleteTLSKey(uint32_t key) { + LOG_USE_MODULE(RuntimeLinker); + + std::unique_lock const lock(m_mutex_int); + + auto destructor = m_dtvKeys[key].destructor; + for (auto obj: m_threadList) { + auto dtv = pthread::getDTV(obj.second); + dtv[key] = 0; + } + + m_dtvKeys[key].used = false; + m_dtvKeys[key].destructor = nullptr; + + LOG_DEBUG(L"<- TLS Key:%d thread:%d", key, pthread::getThreadId()); +} + +void RuntimeLinker::destroyTLSKeys(uint8_t* obj) { + auto pDtvKey = pthread::getDTV(obj); + + std::unique_lock const lock(m_mutex_int); + + for (uint64_t n = m_countcreatePrograms; n < m_dtvKeys.size(); ++n, ++pDtvKey) { + if (m_dtvKeys[n].destructor != nullptr) { + if (pDtvKey[n] != 0) m_dtvKeys[n].destructor((void*)pDtvKey[n]); + } + } + + m_threadList.erase(pthread::getThreadId(obj)); +} + +void RuntimeLinker::stopModules() { + LOG_USE_MODULE(RuntimeLinker); + + // Stop Modules + for (auto& prog: m_programList) { + LOG_INFO(L"Stopping module %s", prog.first->filename.c_str()); + // prog.second->dtDeinit(prog.first->baseVaddr); + for (auto& c: prog.first->cxa) { + c.destructor_func(c.destructor_object); + } + prog.first->cxa.clear(); + } + + // // Custom Libs + // for (auto const& item: m_libHandles) { + // unloadModule(item.second); + // } +} + +void RuntimeLinker::stopModule(int moduleId) { + LOG_USE_MODULE(RuntimeLinker); + + for (auto itProg = m_programList.begin(); itProg != m_programList.end(); ++itProg) { + if (itProg->first->id == moduleId) { + LOG_INFO(L"Stopping module %s", itProg->first->filename.c_str()); + for (auto& c: itProg->first->cxa) { + c.destructor_func(c.destructor_object); + } + itProg->first->cxa.clear(); + + m_programList.erase(itProg); + break; + } + } +} + +void RuntimeLinker::callInitProgramms() { + LOG_USE_MODULE(RuntimeLinker); + + struct CallGraph { + Program* const program; + IFormat* const iFormat; + std::vector childs; + + CallGraph(Program* program, IFormat* format): program(program), iFormat(format) {} + }; + + auto itMainProg = m_programList.begin(); + CallGraph callGraph({itMainProg->first.get(), itMainProg->second.get()}); + // Get dependencies + for (auto const& impLib: itMainProg->second->getImportedLibs()) { + for (auto itImp = std::next(m_programList.begin()); itImp != m_programList.end(); ++itImp) { + auto const& expLib = itImp->second->getExportedLibs(); + if (expLib.find(impLib.first) != expLib.end()) { + LOG_DEBUG(L"%s needs %S", itMainProg->first->filename.c_str(), impLib.first.data()); + callGraph.childs.push_back({itImp->first.get(), itImp->second.get()}); + break; + } + } + } + + for (auto& parent: callGraph.childs) { + auto const& impParent = parent.iFormat->getImportedLibs(); + for (auto itImp = std::next(m_programList.begin()); itImp != m_programList.end(); ++itImp) { + for (auto const& expLib: itImp->second->getExportedLibs()) { + if (impParent.find(expLib.first) != impParent.end()) { + LOG_DEBUG(L"%s needs %s", parent.program->filename.c_str(), itImp->first->filename.c_str()); + parent.childs.push_back({itImp->first.get(), itImp->second.get()}); + } + } + } + } + //- dependencies + + // Startup modules + auto const startModule = [=](CallGraph const& item) { + if (item.program->started) return; + item.program->started = true; + + LOG_INFO(L"Starting %s", item.program->filename.c_str()); + item.iFormat->dtInit(item.program->baseVaddr); + }; + + for (auto& parent: callGraph.childs) { + for (auto& child: parent.childs) { + startModule(child); + } + startModule(parent); + } +} + +void RuntimeLinker::initTlsStaticBlock() { + LOG_USE_MODULE(RuntimeLinker); + + // Set TLS Static Block + size_t tlsStaticSize = 0; + { + int64_t offsetPre = 0; + for (auto const& prog: m_programList) { + offsetPre = util::alignDown(offsetPre - prog.first->tls.sizeImage, prog.first->tls.alignment); + } + tlsStaticSize = util::alignUp(std::abs(offsetPre), 32); + } + + m_tlsStaticInitBlock.resize(tlsStaticSize); + int64_t offset = tlsStaticSize; + uint32_t indexCount = 0; + for (auto const& prog: m_programList) { + offset = util::alignDown(offset - prog.first->tls.sizeImage, prog.first->tls.alignment); + + prog.first->moduleInfoEx.tls_index = ++indexCount; // has to start with 1 + prog.first->moduleInfoEx.tls_offset = offset; + + prog.first->tls.offset = offset; + prog.first->tls.index = prog.first->moduleInfoEx.tls_index; + + m_dtvKeys[prog.first->tls.index].used = true; + m_dtvKeys[prog.first->tls.index].destructor = nullptr; + + LOG_DEBUG(L"%s| tls index:%u offset:0x%08llx alignment:%d", prog.first->filename.c_str(), prog.first->tls.index, prog.first->tls.offset, + prog.first->tls.alignment); + } + // - + + assert((int64_t)offset >= 0); +} + +void RuntimeLinker::setupTlsStaticBlock() { + for (auto const& prog: m_programList) { + memcpy(&m_tlsStaticInitBlock[prog.first->tls.offset], (void*)prog.first->tls.vaddrImage, prog.first->tls.sizeImage); + } +} + +bool RuntimeLinker::interceptInternal(Program* prog, uintptr_t progoffset, uintptr_t iaddr) { +#pragma pack(push, 1) + + struct jumper { + const uint16_t movrax = 0xb848; + uint64_t addr; + const uint16_t jmprax = 0xe0ff; + }; + +#pragma pack(pop) + + const auto progaddr = prog->baseVaddr + progoffset; + + int iProts[2] = {0, 0}; + + const jumper j = {.addr = iaddr}; + + if (!memory::protect(progaddr, sizeof(jumper), 7 /* Set exec, read and write prot to the program's code */, &iProts[0])) return false; + ::memcpy((void*)progaddr, (const void*)&j, sizeof(jumper)); // Copy the jumper to the prgoram + if (!memory::protect(progaddr, sizeof(jumper), iProts[0] /* Restore the old prot */, &iProts[1])) return false; + + return true; +} + +uintptr_t RuntimeLinker::execute() { + LOG_USE_MODULE(RuntimeLinker); + LOG_INFO(L"Execute()"); + + initTlsStaticBlock(); + // pthread::initSelfForMainThread(m_tlsStaticInitBlock.size()); // call before runtimelinker + // initTLS(pthread::getSelf()); + + // Get and load additional Modules needed + { + for (auto const& prog: m_programList) { + LOG_DEBUG(L"Load for %s", prog.first->filename.c_str()); + for (auto const& impLib: prog.second->getImportedLibs()) { + loadModules(impLib.first); + } + } + } + // - load Modules + + // Relocate all (Set Imported Symbols) + m_invalidMemoryAddr = memory::alloc(INVALID_MEMORY, 4096, 0); + for (auto& prog: m_programList) { + if (prog.first) prog.second->relocate(prog.first.get(), m_invalidMemoryAddr, ""); + } + + setupTlsStaticBlock(); // relocate may init tls -> copy after relocate + + uintptr_t const entryAddr = m_programList.begin()->first->entryOffAddr + m_programList.begin()->first->baseVaddr; + LOG_INFO(L"entry:0x%08llx", entryAddr); + + return entryAddr; +} + +IRuntimeLinker& accessRuntimeLinker() { + static RuntimeLinker inst; + return inst; +} diff --git a/core/runtime/runtimeLinker.h b/core/runtime/runtimeLinker.h new file mode 100644 index 00000000..6fe03fdc --- /dev/null +++ b/core/runtime/runtimeLinker.h @@ -0,0 +1,64 @@ +#pragma once +#include "formats/IFormat.h" +#include "formats/ISymbols.h" +#include "program.h" +#include "runtimeExport.h" + +#include +#include + +class IRuntimeLinker: public IRuntimeExport { + public: + virtual std::unique_ptr createProgram(std::filesystem::path const filename, uint64_t const baseSize, uint64_t const baseSizeAligned, + uint64_t const alocSize, bool useStaticTLS) = 0; + + virtual uint64_t getAddrInvalidMemory() = 0; + + virtual void callInitProgramms() = 0; + + virtual Program* addProgram(std::unique_ptr&& prog, std::shared_ptr format) = 0; + + virtual Program* findProgram(uint64_t vaddr) = 0; + virtual Program* findProgramById(size_t id) const = 0; + + virtual uintptr_t execute() = 0; + virtual void stopModules() = 0; + + virtual void stopModule(int id) = 0; + + virtual void addExport(std::unique_ptr&& symbols) = 0; + + virtual bool interceptInternal(Program* prog, uintptr_t progaddr, uintptr_t iaddr) = 0; + virtual void interceptAdd(uintptr_t addr, std::string_view name, std::string_view libraryName, std::string_view modulName) = 0; + virtual uintptr_t interceptGetAddr(uintptr_t addr) const = 0; + + virtual Symbols::IResolve const* getIResolve(std::string_view libName) const = 0; + + virtual uintptr_t checkIntercept(uintptr_t vaddr, std::string_view symName, std::string_view libName, std::string_view modName) const = 0; + + virtual std::vector> getExecSections() const = 0; + + /** + * @brief get access to the main programm. (unique_ptr) + * + * @return Program* nullptr if no main program is missing + */ + virtual Program* accessMainProg() = 0; + + virtual ~IRuntimeLinker() = default; +}; + +SYSV_ABI void* tlsMainGetAddr(int32_t offset); +SYSV_ABI void* tlsMainGetAddr64(int64_t offset); + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif +__APICALL IRuntimeLinker& accessRuntimeLinker(); + +__APICALL std::unique_ptr buildParser_Elf64(std::filesystem::path&& path, std::unique_ptr&& file); +#undef __APICALL diff --git a/core/runtime/util/exceptionHandler.cpp b/core/runtime/util/exceptionHandler.cpp new file mode 100644 index 00000000..bb900428 --- /dev/null +++ b/core/runtime/util/exceptionHandler.cpp @@ -0,0 +1,231 @@ +#include "exceptionHandler.h" + +#include "../runtimeLinker.h" +#include "core/memory/memory.h" +#include "core/runtime/util/virtualmemory.h" +#include "logging.h" +#include "utility/progloc.h" + +#include +#include +#include +// clang-format off +#include +#include +#include +// clang-format on + +LOG_DEFINE_MODULE(ExceptionHandler); + +namespace { + +struct UnwindInfo { + uint8_t Version : 3; + uint8_t Flags : 5; + uint8_t SizeOfProlog; + uint8_t CountOfCodes; + uint8_t FrameRegister : 4; + uint8_t FrameOffset : 4; + ULONG ExceptionHandler; + void* ExceptionData; +}; + +struct JmpHandlerData { + void setHandler(uint64_t func) { *(uint64_t*)(&m_code[2]) = func; } + + // mov rax, 0x1122334455667788 + // jmp rax + uint8_t m_code[16] = {0x48, 0xB8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xFF, 0xE0}; +}; + +struct ExData { + JmpHandlerData handlerCode; + RUNTIME_FUNCTION runtimeFunction; + UnwindInfo unwindInfo; +}; + +std::optional> findModule(uint64_t address) { + + std::unordered_map modules; + + HANDLE hProcess = GetCurrentProcess(); + HMODULE hModules[1024]; + DWORD cbNeeded; + + if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) { + MODULEINFO mi; + + for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { + GetModuleInformation(hProcess, hModules[i], &mi, sizeof(mi)); + + if ((uint64_t)mi.lpBaseOfDll <= address && address < ((uint64_t)mi.lpBaseOfDll + mi.SizeOfImage)) { + char moduleName[MAX_PATH]; + if (GetModuleFileName(hModules[i], moduleName, MAX_PATH)) { + return std::make_pair((uint64_t)mi.lpBaseOfDll, std::string(moduleName)); + } + } + } + } + + auto prog = accessRuntimeLinker().findProgram(address); + if (prog != nullptr) { + return std::make_pair(prog->baseVaddr, prog->filename.string()); + } + + return std::nullopt; +} + +bool tryGetSymName(PSYMBOL_INFO sym, const void* addr, std::string& name) { + auto proc = GetCurrentProcess(); + + static std::once_flag init; + std::call_once(init, [proc]() { + LOG_USE_MODULE(ExceptionHandler); + auto dir = std::filesystem::path(util::getProgramLoc()) / "debug"; + if (!SymInitializeW(proc, dir.c_str(), true)) { + LOG_ERR(L"Failed to initialize the debug information"); + } + }); + + // Just to be sure that the previous call won't break anything + ::memset(sym, 0, sizeof(SYMBOL_INFO)); + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = 1024; + + DWORD disp = 0; + + IMAGEHLP_LINE64 line = { + .SizeOfStruct = sizeof(IMAGEHLP_LINE64), + }; + + if (SymFromAddr(proc, (uint64_t)addr, nullptr, sym)) { + name.assign(sym->Name); + if (SymGetLineFromAddr64(proc, sym->Address, &disp, &line)) { + name += std::format(" ({}:{}:{})", line.FileName, line.LineNumber, disp); + } + + return true; + } + + return false; +} + +void stackTrace(uint64_t addr) { + LOG_USE_MODULE(ExceptionHandler); + + bool foundStart = false; + size_t countTraces = 0; + + std::vector sym; + sym.resize(1024 + sizeof(SYMBOL_INFO)); + + // Stack trace + for (auto& trace: boost::stacktrace::basic_stacktrace()) { + if (!foundStart) { + + if ((uint64_t)trace.address() == addr) + foundStart = true; + else + continue; + } + if (++countTraces > 4) break; + + if (trace.empty()) { + LOG_ERR(L"????"); + } + + std::string fileName; + + if (!tryGetSymName((PSYMBOL_INFO)sym.data(), trace.address(), fileName)) { + // Failed to get the source file name, clearing the string + fileName.clear(); + } + + auto optModuleInfo = findModule((uint64_t)trace.address()); + + if (fileName.empty()) { + auto optModuleInfo = findModule((uint64_t)trace.address()); + + if (optModuleInfo) { + LOG_ERR(L"offset:0x%08llx\t base:0x%08llx\t%S", (uint64_t)trace.address() - optModuleInfo->first, optModuleInfo->first, optModuleInfo->second.c_str()); + } else { + LOG_ERR(L"0x%08llx\t", trace.address()); + } + } else { + if (optModuleInfo) { + LOG_ERR(L"0x%08llx base:0x%08llx %S\n\t%S", trace.address(), optModuleInfo->first, optModuleInfo->second.c_str(), fileName.c_str()); + } else { + LOG_ERR(L"0x%08llx\n\t %S", trace.address(), fileName.c_str()); + } + } + } + // - +} + +enum class AccessViolationType { Unknown, Read, Write, Execute }; + +static EXCEPTION_DISPOSITION DefaultExceptionHandler(PEXCEPTION_RECORD exception_record, ULONG64 /*EstablisherFrame*/, PCONTEXT /*ContextRecord*/, + PDISPATCHER_CONTEXT dispatcher_context) { + LOG_USE_MODULE(ExceptionHandler); + + auto exceptionAddr = (uint64_t)(exception_record->ExceptionAddress); + auto violationAddr = exception_record->ExceptionInformation[1]; + + stackTrace(exceptionAddr); + + uint64_t baseAddr = 0; + std::string moduleName; + + auto optModuleInfo = findModule(exceptionAddr); + if (optModuleInfo) { + baseAddr = optModuleInfo->first; + moduleName = optModuleInfo->second; + } + + AccessViolationType violationType; + switch (exception_record->ExceptionInformation[0]) { + case 0: violationType = AccessViolationType::Read; break; + case 1: violationType = AccessViolationType::Write; break; + case 8: violationType = AccessViolationType::Execute; break; + default: LOG_CRIT(L"unknown exception at 0x%08llx, module base:0x%08llx %S", exceptionAddr, baseAddr, moduleName.data()); break; + } + + LOG_CRIT(L"Access violation: %S at addr:0x%08llx info:0x%08llx %S, module base:0x%08llx %S", magic_enum::enum_name(violationType).data(), exceptionAddr, + violationAddr, (violationAddr == accessRuntimeLinker().getAddrInvalidMemory() ? L"(Unpatched object)" : L""), baseAddr, moduleName.data()); + //); + + return ExceptionContinueExecution; +} + +} // namespace + +namespace ExceptionHandler { +uint64_t getAllocSize() { + return sizeof(ExData); +} + +void install(uint64_t imageAddr, uint64_t handlerDstAddr, uint64_t imageSize) { + auto functionTable = new ((void*)handlerDstAddr) ExData; + + auto& func = functionTable->runtimeFunction; + func.BeginAddress = 0; + func.EndAddress = imageSize; + func.UnwindData = (uint64_t)&functionTable->unwindInfo - imageAddr; + + auto& unwindData = functionTable->unwindInfo; + unwindData.Version = 1; + unwindData.Flags = UNW_FLAG_EHANDLER; + unwindData.SizeOfProlog = 0; + unwindData.CountOfCodes = 0; + unwindData.FrameRegister = 0; + unwindData.FrameOffset = 0; + unwindData.ExceptionHandler = (uint64_t)&functionTable->handlerCode - imageAddr; + unwindData.ExceptionData = nullptr; + + functionTable->handlerCode.setHandler((uint64_t)DefaultExceptionHandler); + + RtlAddFunctionTable(&functionTable->runtimeFunction, 1, imageAddr); + + flushInstructionCache((uint64_t)&functionTable->handlerCode, sizeof(JmpHandlerData)); +} +} // namespace ExceptionHandler diff --git a/core/runtime/util/exceptionHandler.h b/core/runtime/util/exceptionHandler.h new file mode 100644 index 00000000..457b68d0 --- /dev/null +++ b/core/runtime/util/exceptionHandler.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +namespace ExceptionHandler { + +uint64_t getAllocSize(); +void install(uint64_t imageAddr, uint64_t handlerDstAddr, uint64_t imageSize); + +} // namespace ExceptionHandler \ No newline at end of file diff --git a/core/runtime/util/moduleLoader.cpp b/core/runtime/util/moduleLoader.cpp new file mode 100644 index 00000000..7efd0394 --- /dev/null +++ b/core/runtime/util/moduleLoader.cpp @@ -0,0 +1,69 @@ +#include "moduleLoader.h" + +#include "logging.h" + +#include +#include +#include +#include +#include + +#undef min + +LOG_DEFINE_MODULE(ModuleLoader); + +std::pair> loadModule(const char* libName, const wchar_t* filepath, int libVersion) { + LOG_USE_MODULE(ModuleLoader); + + HMODULE hModule = LoadLibraryW(filepath); + if (hModule == NULL) { + LOG_ERR(L"Couldn't load library %S, err:%d", filepath, GetLastError()); + return {}; + } + + auto const imageDosHeader = (PIMAGE_DOS_HEADER)hModule; + if (imageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + LOG_ERR(L"Wrong image signature %x for %S", imageDosHeader->e_magic, filepath); + return {}; + } + + auto const imageNtHeaders = (PIMAGE_NT_HEADERS)((unsigned char*)imageDosHeader + imageDosHeader->e_lfanew); + if (imageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { + LOG_ERR(L"Wrong PE signature %x for %S", imageDosHeader->e_magic, filepath); + return {}; + } + + PIMAGE_OPTIONAL_HEADER imageOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&imageNtHeaders->OptionalHeader; + PIMAGE_DATA_DIRECTORY imageExportDataDirectory = &(imageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]); + PIMAGE_EXPORT_DIRECTORY imageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((unsigned char*)hModule + imageExportDataDirectory->VirtualAddress); + + DWORD numberOfNames = imageExportDirectory->NumberOfNames; + + PDWORD exportAddressTable = (PDWORD)((unsigned char*)hModule + imageExportDirectory->AddressOfFunctions); + PWORD nameOrdinalsPointer = (PWORD)((unsigned char*)hModule + imageExportDirectory->AddressOfNameOrdinals); + PDWORD exportNamePointerTable = (PDWORD)((unsigned char*)hModule + imageExportDirectory->AddressOfNames); + + auto libInfo = std::make_unique(libName, libVersion, "", 1, 0); + + for (size_t n = 0; n < numberOfNames; n++) { + auto name = std::string_view((const char*)((unsigned char*)hModule + exportNamePointerTable[n])); + if (name.ends_with("=")) { + std::string nid(name.data(), 0, name.size() - 1); + auto const pFunc = (uint64_t)((unsigned char*)hModule + exportAddressTable[nameOrdinalsPointer[n]]); + + libInfo->symbolsMap.emplace(std::make_pair(nid, Symbols::SymbolExport::Symbol {nid, {}, pFunc, Symbols::SymbolType::Func})); + } else if (name == "MODULE_NAME") { + auto const val = *(char const**)((unsigned char*)hModule + exportAddressTable[nameOrdinalsPointer[n]]); + libInfo->modulName = val; + } + } + + if (libInfo->modulName.empty()) { + LOG_CRIT(L"No Module name in %S", libName); + } + return {(void*)hModule, std::move(libInfo)}; +} + +void unloadModule(void* handle) { + FreeLibrary((HMODULE)handle); +} diff --git a/core/runtime/util/moduleLoader.h b/core/runtime/util/moduleLoader.h new file mode 100644 index 00000000..81224629 --- /dev/null +++ b/core/runtime/util/moduleLoader.h @@ -0,0 +1,11 @@ +#pragma once +#include "../formats/ISymbols.h" + +#include +#include +#include +#include + +std::pair> loadModule(const char* name, const wchar_t* filepath, int libVersion); + +void unloadModule(void* handle); diff --git a/core/runtime/util/plt.h b/core/runtime/util/plt.h new file mode 100644 index 00000000..65270217 --- /dev/null +++ b/core/runtime/util/plt.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#pragma pack(1) + +namespace { +struct JmpWithIndex { + void setHandler(void* handler) { + auto handlerAddr = (int64_t)(handler); + auto ripAddr = (int64_t)(&m_code[10]); + auto offset64 = handlerAddr - ripAddr; + auto offset32 = (uint32_t)offset64; + + *(uint32_t*)(&m_code[6]) = offset32; + } + + void setIndex(uint32_t index) { *(uint32_t*)(&m_code[1]) = index; } + + static uint64_t getSize() { return 16; } + + private: + // 68 00 00 00 00 push + // E9 E0 FF FF FF jmp + uint8_t m_code[16] = {0x68, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90}; +}; + +} // namespace + +struct CallPlt { + explicit CallPlt(uint32_t table_size) { + for (uint32_t index = 0; index < table_size; index++) { + auto* c = new ((uint8_t*)getAddr(index)) JmpWithIndex; + c->setIndex(index); + c->setHandler(this); + } + } + + uint64_t getAddr(uint32_t index) { return (uint64_t)(&m_code[32] + JmpWithIndex::getSize() * index); } + + void setPltGot(uint64_t vaddr) { *(uint64_t*)(&m_code[2]) = vaddr; } + + static uint64_t getSize(uint32_t tableSize) { return 32 + JmpWithIndex::getSize() * tableSize; } + + private: + // 0: 49 bb 88 77 66 55 44 movabs r11,0x1122334455667788 + // 7: 33 22 11 + // a: 41 ff 73 08 push QWORD PTR [r11+0x8] + // e: 41 ff 63 10 jmp QWORD PTR [r11+0x10] + uint8_t m_code[32] = {0x49, 0xBB, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x41, 0xFF, + 0x73, 0x08, 0x41, 0xFF, 0x63, 0x10, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90}; +}; + +#pragma pack() diff --git a/core/runtime/util/virtualmemory.cpp b/core/runtime/util/virtualmemory.cpp new file mode 100644 index 00000000..6f95e63d --- /dev/null +++ b/core/runtime/util/virtualmemory.cpp @@ -0,0 +1,38 @@ +#include "virtualmemory.h" + +#include "core/memory/memory.h" +#include "logging.h" + +#include + +LOG_DEFINE_MODULE(VIRTUALMEMORY); + +namespace {} // namespace + +bool flushInstructionCache(uint64_t address, uint64_t size) { + LOG_USE_MODULE(VIRTUALMEMORY); + if (::FlushInstructionCache(GetCurrentProcess(), reinterpret_cast(static_cast(address)), size) == 0) { + LOG_ERR(L"FlushInstructionCache() failed: 0x%04x", static_cast(GetLastError())); + return false; + } + return true; +} + +bool patchReplace(uint64_t vaddr, uint64_t const value) { + int oldMode; + memory::protect(vaddr, 8, SceProtRead | SceProtWrite, &oldMode); + + auto* ptr = reinterpret_cast(vaddr); + + bool ret = (*ptr != value); + + *ptr = value; + + memory::protect(vaddr, 8, oldMode); + + if (memory::isExecute(oldMode)) { + flushInstructionCache(vaddr, 8); + } + + return ret; +} \ No newline at end of file diff --git a/core/runtime/util/virtualmemory.h b/core/runtime/util/virtualmemory.h new file mode 100644 index 00000000..38bbc1ec --- /dev/null +++ b/core/runtime/util/virtualmemory.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +bool flushInstructionCache(uint64_t address, uint64_t size); +bool patchReplace(uint64_t vaddr, uint64_t const value); diff --git a/core/systemContent/CMakeLists.txt b/core/systemContent/CMakeLists.txt new file mode 100644 index 00000000..4164fd24 --- /dev/null +++ b/core/systemContent/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(systemContent OBJECT +systemContent.cpp +) + +add_dependencies(systemContent third_party psOff_utility) + +file(COPY systemContent.h DESTINATION ${DST_INCLUDE_DIR}/systemContent) \ No newline at end of file diff --git a/core/systemContent/systemContent.cpp b/core/systemContent/systemContent.cpp new file mode 100644 index 00000000..ccf284ff --- /dev/null +++ b/core/systemContent/systemContent.cpp @@ -0,0 +1,146 @@ +#define __APICALL_EXTERN +#include "systemContent.h" +#undef __APICALL_EXTERN + +#include "logging.h" +#include "utility/utility.h" + +#include +#include +#include + +LOG_DEFINE_MODULE(SYSTEMCONTENT); + +namespace { +struct ParamInfo { + uint16_t type; + uint32_t size1; + uint32_t size2; + uint16_t nameOffset; + uint32_t valueOffset; +}; + +std::unordered_map>> loadSFO(std::filesystem::path const& root) { + LOG_USE_MODULE(SYSTEMCONTENT); + + auto file = util::openFile(root / L"param.sfo"); + if (!file) { + LOG_ERR(L"No param.sfo"); + return {}; + } + + // Check Magic + { + std::array buffer; + if (!util::readFile(file.get(), L"param.sfo", (char*)buffer.data(), buffer.size())) { + LOG_ERR(L"Couldn't read .sfo magic"); + return {}; + } + + uint32_t const magic1 = *(uint32_t const*)&buffer[0]; + uint32_t const magic2 = *(uint32_t const*)&buffer[4]; + + if (magic1 != 0x46535000 && magic2 != 0x00000101) { + LOG_ERR(L".sfo magic error| 0x%08x 0x%08x", magic1, magic2); + return {}; + } + } + // - Magic + + auto [nameOffset, valueOffset, numParams] = [&]() -> std::tuple { + std::array buffer; + if (!util::readFile(file.get(), L"param.sfo", (char*)buffer.data(), buffer.size())) { + LOG_ERR(L"Couldn't read .sfo Header"); + return {}; + } + return std::make_tuple(*(uint32_t const*)&buffer[0], *(uint32_t const*)&buffer[4], *(uint32_t const*)&buffer[8]); + }(); + + // Read Params + std::vector params(numParams); + { + std::array buffer; + for (uint16_t n = 0; n < numParams; ++n) { + if (!util::readFile(file.get(), L"param.sfo", (char*)buffer.data(), buffer.size())) { + LOG_ERR(L"Couldn't read .sfo paramInfo %llu", n); + return {}; + } + + params[n] = ParamInfo {.type = *(uint16_t const*)&buffer[2], + .size1 = *(uint32_t const*)&buffer[4], + .size2 = *(uint32_t const*)&buffer[8], + .nameOffset = *(uint16_t const*)&buffer[0], + .valueOffset = *(uint32_t const*)&buffer[12]}; + + if (params[n].type != 0x0204 && params[n].type != 0x0404) { + LOG_ERR(L"unknown .sfo param %u type:%u", n, params[n].type); + return {}; + } + } + } + // - params + + // read nameList + const uint32_t nameListSize = valueOffset - nameOffset; + std::vector nameList(nameListSize); + file->seekg(nameOffset); + if (!util::readFile(file.get(), L"param.sfo", (char*)nameList.data(), nameList.size())) { + LOG_ERR(L"Couldn't read .sfo nameList"); + return {}; + } + + std::unordered_map>> paramList; + for (uint16_t n = 0; n < numParams; ++n) { + auto const name = std::string((char*)&nameList[params[n].nameOffset]); + + file->seekg(valueOffset + params[n].valueOffset, std::ios_base::beg); + std::vector paramValue(params[n].size1); + + if (!util::readFile(file.get(), L"param.sfo", (char*)paramValue.data(), paramValue.size())) { + LOG_ERR(L"Couldn't read .sfo param %u value"); + return {}; + } + + if (params[n].type == 0x0404) + LOG_DEBUG(L"Read .sfo param[%u] %S| size:%u value(uint):%u", n, name.data(), paramValue.size(), *(uint32_t*)paramValue.data()); + else if (params[n].type == 0x0204) + LOG_DEBUG(L"Read .sfo param[%u] %S| size:%u value(string):%S", n, name.data(), paramValue.size(), (char*)paramValue.data()); + paramList[std::move(name)] = std::make_pair(params[n].type, std::move(paramValue)); + } + return paramList; +} +} // namespace + +class SystemContent: public ISystemContent { + private: + std::unordered_map>> m_sfoParams; + + public: + void init(std::filesystem::path const& path) final; + + std::optional getInt(std::string_view name) const final { + if (auto it = m_sfoParams.find(name.data()); it != m_sfoParams.end()) { + if (it->second.first != 0x0404) return {}; + return {*(uint32_t*)it->second.second.data()}; + } + return {}; + } + + std::optional getString(std::string_view name) const final { + if (auto it = m_sfoParams.find(name.data()); it != m_sfoParams.end()) { + if (it->second.first != 0x0204) return {}; + return {(const char*)it->second.second.data()}; + } + return {}; + } +}; + +void SystemContent::init(std::filesystem::path const& root) { + if (!m_sfoParams.empty()) return; + m_sfoParams = loadSFO(root); +} + +ISystemContent& accessSystemContent() { + static SystemContent inst; + return inst; +} \ No newline at end of file diff --git a/core/systemContent/systemContent.h b/core/systemContent/systemContent.h new file mode 100644 index 00000000..ffa758ac --- /dev/null +++ b/core/systemContent/systemContent.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include + +namespace std::filesystem { +class path; +} + +class ISystemContent { + public: + /** + * @brief Checks if the profided path contain "param.sfo" and reads it + * + * @param path + */ + virtual void init(std::filesystem::path const& path) = 0; + + /** + * @brief Get an int value from the sfo + * + * @param name + * @return std::optional + */ + virtual std::optional getInt(std::string_view name) const = 0; + + /** + * @brief Get an string value from the sfo + * + * @param name + * @return std::optional + */ + virtual std::optional getString(std::string_view name) const = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif +__APICALL ISystemContent& accessSystemContent(); +#undef __APICALL \ No newline at end of file diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt new file mode 100644 index 00000000..6e451349 --- /dev/null +++ b/core/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +enable_testing() + +add_compile_definitions( + BOOST_ALL_NO_LIB +) + +link_libraries(gtest_main gmock gmock_main) +add_link_options(/DEBUG) +link_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_BINARY_DIR}/lib +) + +include_directories( + ${PRJ_SRC_DIR} + + ${Vulkan_INCLUDE_DIRS} + ${PRJ_SRC_DIR}/tools/logging +) + +add_subdirectory(core) + +install(TARGETS semaphore_test RUNTIME DESTINATION .) \ No newline at end of file diff --git a/core/tests/core/CMakeLists.txt b/core/tests/core/CMakeLists.txt new file mode 100644 index 00000000..2cd44415 --- /dev/null +++ b/core/tests/core/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(semaphore) \ No newline at end of file diff --git a/core/tests/core/semaphore/CMakeLists.txt b/core/tests/core/semaphore/CMakeLists.txt new file mode 100644 index 00000000..1aba0e9a --- /dev/null +++ b/core/tests/core/semaphore/CMakeLists.txt @@ -0,0 +1,17 @@ +enable_testing() + +add_executable(semaphore_test + entry.cpp + + ${PRJ_SRC_DIR}/core/kernel/semaphore_fifo.cpp +) + +add_test(NAME semaphore_test COMMAND semaphore_test) + +add_dependencies(semaphore_test third_party psOff_utility logging_stub) + +target_link_libraries(semaphore_test PRIVATE + logging_stub.lib + libboost_thread + libboost_chrono +) \ No newline at end of file diff --git a/core/tests/core/semaphore/entry.cpp b/core/tests/core/semaphore/entry.cpp new file mode 100644 index 00000000..2d04aafb --- /dev/null +++ b/core/tests/core/semaphore/entry.cpp @@ -0,0 +1,279 @@ + +#include "core/kernel/semaphore.h" +#include "modules_include/common.h" + +#include +#include +#include + +DEFINE_FFF_GLOBALS; + +namespace pthread { +FAKE_VALUE_FUNC(int, getThreadId); +} + +TEST(core_semaphore, init) { + RESET_FAKE(pthread::getThreadId); + pthread::getThreadId_fake.return_val = 4; + + constexpr int initCount = 1; + constexpr int maxCount = 10; + + { + auto sem = createSemaphore_fifo("test", initCount, maxCount); + EXPECT_TRUE(sem); + + EXPECT_EQ(sem->getSignalCounter(), initCount); + } + + { + auto sem = createSemaphore_fifo(nullptr, initCount, maxCount); + EXPECT_TRUE(sem); + + EXPECT_EQ(sem->getSignalCounter(), initCount); + } +} + +TEST(core_semaphore, polling_1) { + RESET_FAKE(pthread::getThreadId); + pthread::getThreadId_fake.return_val = 4; + + constexpr int initCount = 1; + constexpr int maxCount = 10; + + auto sem = createSemaphore_fifo("test", initCount, maxCount); + + auto fut = std::async(std::launch::async, [&sem] { + { + auto res = sem->poll(2); + EXPECT_EQ(res, getErr(ErrCode::_EAGAIN)); + } + { + auto res = sem->poll(1); + EXPECT_EQ(res, Ok); + + EXPECT_EQ(sem->getSignalCounter(), 0); + } + { + auto res = sem->poll(2); + EXPECT_EQ(res, getErr(ErrCode::_EAGAIN)); + } + { + auto resSignal = sem->signal(2); + EXPECT_EQ(resSignal, Ok); + + auto res = sem->poll(2); + EXPECT_EQ(res, Ok); + + EXPECT_EQ(sem->getSignalCounter(), 0); + } + + { + auto resSignal = sem->signal(4); + EXPECT_EQ(resSignal, Ok); + + auto res1 = sem->poll(2); + EXPECT_EQ(res1, Ok); + EXPECT_EQ(sem->getSignalCounter(), 2); + + auto res2 = sem->poll(2); + EXPECT_EQ(res2, Ok); + EXPECT_EQ(sem->getSignalCounter(), 0); + } + }); + + auto res = fut.wait_for(std::chrono::milliseconds(10)); + EXPECT_NE(res, std::future_status::timeout); +} + +TEST(core_semaphore, signal_1) { + RESET_FAKE(pthread::getThreadId); + pthread::getThreadId_fake.return_val = 4; + + constexpr int initCount = 1; + constexpr int maxCount = 10; + + auto sem = createSemaphore_fifo("test", initCount, maxCount); + + { // Check wait instant release + auto resSignal = sem->signal(1); + EXPECT_EQ(resSignal, Ok); + EXPECT_EQ(sem->getSignalCounter(), 2); + + auto fut = std::async(std::launch::async, [&sem] { + { + auto res = sem->wait(2, nullptr); // wait forever + EXPECT_EQ(res, Ok); + } + }); + + auto res = fut.wait_for(std::chrono::milliseconds(10)); + EXPECT_NE(res, std::future_status::timeout); + } + + EXPECT_EQ(sem->getSignalCounter(), 0); + + { // Check timeout + auto resSignal = sem->signal(1); + EXPECT_EQ(resSignal, Ok); + EXPECT_EQ(sem->getSignalCounter(), 1); + + auto fut = std::async(std::launch::async, [&sem] { + { + uint32_t micros = 1; + auto res = sem->wait(2, µs); // wait timeout + EXPECT_EQ(res, getErr(ErrCode::_ETIMEDOUT)); + } + }); + + auto res = fut.wait_for(std::chrono::milliseconds(10)); + EXPECT_NE(res, std::future_status::timeout); + } + EXPECT_EQ(sem->getSignalCounter(), 1); + + { // Check wait signal afterwards + auto fut = std::async(std::launch::async, [&sem] { + { + auto res = sem->wait(2, nullptr); // wait forever + EXPECT_EQ(res, Ok); + } + }); + std::this_thread::sleep_for( + std::chrono::microseconds(100)); // this or mock condition var + + auto resSignal = sem->signal(1); + EXPECT_EQ(resSignal, Ok); + EXPECT_EQ(sem->getSignalCounter(), 2); + + auto res = fut.wait_for(std::chrono::milliseconds(10)); + EXPECT_NE(res, std::future_status::timeout); + } +} + +TEST(core_semaphore, signal_2) { + RESET_FAKE(pthread::getThreadId); + + pthread::getThreadId_fake.custom_fake = []() { + static int counter = 0; + return ++counter; + }; + + constexpr int initCount = 0; + constexpr int maxCount = 10; + + auto sem = createSemaphore_fifo("test", initCount, maxCount); + EXPECT_EQ(sem->getSignalCounter(), 0); + + size_t countItems = 0; + { // Check wait signal afterwards (sequenze release) + auto fut1 = std::async(std::launch::async, [&sem, &countItems] { + { + auto res = sem->wait(2, nullptr); // wait forever + ++countItems; + EXPECT_EQ(res, Ok); + } + }); + auto fut2 = std::async(std::launch::async, [&sem, &countItems] { + { + auto res = sem->wait(2, nullptr); // wait forever + ++countItems; + EXPECT_EQ(res, Ok); + } + }); + std::this_thread::sleep_for( + std::chrono::microseconds(100)); // this or mock condition var + + { + auto resSignal = sem->signal(2); + EXPECT_EQ(resSignal, Ok); + } + + auto res1 = fut1.wait_for(std::chrono::microseconds(100)); + EXPECT_NE(res1, std::future_status::timeout); + EXPECT_EQ(sem->getSignalCounter(), 0); + EXPECT_EQ(countItems, 1); + + { + auto resSignal = sem->signal(2); + EXPECT_EQ(resSignal, Ok); + } + + auto res2 = fut2.wait_for(std::chrono::microseconds(100)); + EXPECT_NE(res2, std::future_status::timeout); + EXPECT_EQ(countItems, 2); + } + + { // Check wait signal afterwards (direct release) + auto fut1 = std::async(std::launch::async, [&sem] { + { + auto res = sem->wait(2, nullptr); // wait forever + EXPECT_EQ(res, Ok); + } + }); + auto fut2 = std::async(std::launch::async, [&sem] { + { + auto res = sem->wait(2, nullptr); // wait forever + EXPECT_EQ(res, Ok); + } + }); + std::this_thread::sleep_for( + std::chrono::microseconds(100)); // this or mock condition var + + { + auto resSignal = sem->signal(4); + EXPECT_EQ(resSignal, Ok); + } + + auto res1 = fut1.wait_for(std::chrono::microseconds(100)); + EXPECT_NE(res1, std::future_status::timeout); + + auto res2 = fut2.wait_for(std::chrono::microseconds(100)); + EXPECT_NE(res2, std::future_status::timeout); + EXPECT_EQ(sem->getSignalCounter(), 0); + } + + sem.reset(); +} + +TEST(core_semaphore, signal_exit) { + RESET_FAKE(pthread::getThreadId); + + pthread::getThreadId_fake.custom_fake = []() { + static int counter = 0; + return ++counter; + }; + + constexpr int initCount = 0; + constexpr int maxCount = 10; + + auto sem = createSemaphore_fifo("test", initCount, maxCount); + EXPECT_EQ(sem->getSignalCounter(), 0); + + { // Check wait signal afterwards and exit release + auto fut1 = std::async(std::launch::async, [&sem] { + { + auto res = sem->wait(2, nullptr); // wait forever + EXPECT_EQ(res, Ok); + } + }); + auto fut2 = std::async(std::launch::async, [&sem] { + { + auto res = sem->wait(2, nullptr); // wait forever + EXPECT_EQ(res, getErr(ErrCode::_ECANCELED)); + } + }); + std::this_thread::sleep_for( + std::chrono::microseconds(100)); // this or mock condition var + + auto resSignal = sem->signal(2); + EXPECT_EQ(resSignal, Ok); + + auto res1 = fut1.wait_for(std::chrono::microseconds(100)); + EXPECT_NE(res1, std::future_status::timeout); + EXPECT_EQ(sem->getSignalCounter(), 0); + + auto res2 = fut2.wait_for(std::chrono::microseconds(100)); + EXPECT_EQ(res2, std::future_status::timeout); + sem.reset(); + } +} \ No newline at end of file diff --git a/core/timer/CMakeLists.txt b/core/timer/CMakeLists.txt new file mode 100644 index 00000000..23c11072 --- /dev/null +++ b/core/timer/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(timer OBJECT + timer.cpp +) + +add_dependencies(timer third_party) + +file(COPY timer.h DESTINATION ${DST_INCLUDE_DIR}/timer) \ No newline at end of file diff --git a/core/timer/SysWindowsTimer.h b/core/timer/SysWindowsTimer.h new file mode 100644 index 00000000..a1772a10 --- /dev/null +++ b/core/timer/SysWindowsTimer.h @@ -0,0 +1,111 @@ +#pragma once +#include +#include + +namespace System { + +struct SysTimeStruct { + uint16_t Year; // NOLINT(readability-identifier-naming) + uint16_t Month; // NOLINT(readability-identifier-naming) + uint16_t Day; // NOLINT(readability-identifier-naming) + uint16_t Hour; // NOLINT(readability-identifier-naming) + uint16_t Minute; // NOLINT(readability-identifier-naming) + uint16_t Second; // NOLINT(readability-identifier-naming) + uint16_t Milliseconds; // NOLINT(readability-identifier-naming) + bool is_invalid; // NOLINT(readability-identifier-naming) +}; + +struct SysFileTimeStruct { + FILETIME time; + bool is_invalid; +}; + +// NOLINTNEXTLINE(google-runtime-references) +inline void sys_file_to_system_time_utc(const SysFileTimeStruct& f, SysTimeStruct& t) { + SYSTEMTIME s; + + if (f.is_invalid || (FileTimeToSystemTime(&f.time, &s) == 0)) { + t.is_invalid = true; + return; + } + + t.is_invalid = false; + t.Year = s.wYear; + t.Month = s.wMonth; + t.Day = s.wDay; + t.Hour = s.wHour; + t.Minute = s.wMinute; + t.Second = (s.wSecond == 60 ? 59 : s.wSecond); + t.Milliseconds = s.wMilliseconds; +} + +// NOLINTNEXTLINE(google-runtime-references) +inline void sys_time_t_to_system(time_t t, SysTimeStruct& s) { + SysFileTimeStruct ft {}; + LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000; + ft.time.dwLowDateTime = static_cast(ll); + ft.time.dwHighDateTime = static_cast(static_cast(ll) >> 32u); + ft.is_invalid = false; + sys_file_to_system_time_utc(ft, s); +} + +// NOLINTNEXTLINE(google-runtime-references) +inline void sys_system_to_file_time_utc(const SysTimeStruct& f, SysFileTimeStruct& t) { + SYSTEMTIME s; + + s.wYear = f.Year; + s.wMonth = f.Month; + s.wDay = f.Day; + s.wHour = f.Hour; + s.wMinute = f.Minute; + s.wSecond = f.Second; + s.wMilliseconds = f.Milliseconds; + + t.is_invalid = (f.is_invalid || (SystemTimeToFileTime(&s, &t.time) == 0)); +} + +// Retrieves the current local date and time +// NOLINTNEXTLINE(google-runtime-references) +inline void sys_get_system_time(SysTimeStruct& t) { + SYSTEMTIME s; + GetLocalTime(&s); + + t.is_invalid = false; + t.Year = s.wYear; + t.Month = s.wMonth; + t.Day = s.wDay; + t.Hour = s.wHour; + t.Minute = s.wMinute; + t.Second = (s.wSecond == 60 ? 59 : s.wSecond); + t.Milliseconds = s.wMilliseconds; +} + +// Retrieves the current system date and time in Coordinated Universal Time (UTC). +// NOLINTNEXTLINE(google-runtime-references) +inline void sys_get_system_time_utc(SysTimeStruct& t) { + SYSTEMTIME s; + GetSystemTime(&s); + + t.is_invalid = false; + t.Year = s.wYear; + t.Month = s.wMonth; + t.Day = s.wDay; + t.Hour = s.wHour; + t.Minute = s.wMinute; + t.Second = (s.wSecond == 60 ? 59 : s.wSecond); + t.Milliseconds = s.wMilliseconds; +} + +inline void sys_query_performance_frequency(uint64_t* freq) { + LARGE_INTEGER f; + QueryPerformanceFrequency(&f); + *freq = f.QuadPart; +} + +inline void sys_query_performance_counter(uint64_t* counter) { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + *counter = c.QuadPart; +} + +} // namespace System diff --git a/core/timer/timer.cpp b/core/timer/timer.cpp new file mode 100644 index 00000000..e72b1fab --- /dev/null +++ b/core/timer/timer.cpp @@ -0,0 +1,181 @@ +#define __APICALL_EXTERN +#include "timer.h" +#undef __APICALL_EXTERN + +#include "SysWindowsTimer.h" // todo use boost + +#include +#include + +namespace { +void time2timespec(SceKernelTimespec* ts, double sec) { + ts->tv_sec = static_casttv_sec)>(sec); + ts->tv_nsec = static_casttv_nsec)>((sec - static_cast(ts->tv_sec)) * 1e9); +} +} // namespace + +class Timer: public ITimer { + uint64_t m_startTime; + uint64_t m_PauseTime = 1; + uint64_t m_freq = 0; + + public: + Timer() = default; + + void init() final { + System::sys_query_performance_counter(&m_startTime); + m_PauseTime = 0; + System::sys_query_performance_frequency(&m_freq); + } + + double getTimeS() final { + if (m_PauseTime > 0) { + return (static_cast(m_PauseTime - m_startTime)) / static_cast(m_freq); + } + + uint64_t currentTime = 0; + System::sys_query_performance_counter(¤tTime); + + return (static_cast(currentTime - m_startTime)) / static_cast(m_freq); + } + + double getTimeMs() final { return 1e3 * getTimeS(); } + + uint64_t getTicks() final { + if (m_PauseTime > 0) { + return (m_PauseTime - m_startTime); + } + + uint64_t currentTime = 0; + System::sys_query_performance_counter(¤tTime); + return (currentTime - m_startTime); + } + + uint64_t queryPerformance() final { + uint64_t ret = 0; + System::sys_query_performance_counter(&ret); + return ret; + } + + void pause() final { System::sys_query_performance_counter(&m_PauseTime); } + + void resume() final { + + uint64_t currentTime = 0; + System::sys_query_performance_counter(¤tTime); + + m_startTime += currentTime - m_PauseTime; + m_PauseTime = 0; + } + + uint64_t getFrequency() final { return m_freq; } + + bool getPaused() final { return m_PauseTime > 0; } + + int getTime(SceKernelClockid id, SceKernelTimespec* tp) final; + int getTimeRes(SceKernelClockid id, SceKernelTimespec* tp) final; + int getTimeofDay(SceKernelTimeval* tp) final; + int getTimeZone(SceKernelTimezone* tz) final; +}; + +ITimer& accessTimer() { + static Timer inst; + return inst; +} + +int Timer::getTime(SceKernelClockid id, SceKernelTimespec* tp) { + if (tp == nullptr) { + return getErr(ErrCode::_EFAULT); + } + + using namespace boost::chrono; + auto func = [tp](auto const& now) { + tp->tv_sec = time_point_cast(now).time_since_epoch().count(); + tp->tv_nsec = time_point_cast(now).time_since_epoch().count() % 1000000000; + return Ok; + }; + + switch (id) { + case 0: + case 9: + case 10: return func(system_clock::now()); + case 4: + case 11: + case 12: + case 13: return func(steady_clock::now()); + case 1: + case 2: + case 5: + case 7: + case 8: { + double const ts = accessTimer().getTimeS(); + time2timespec(tp, ts); + return Ok; + } + case 14: return func(thread_clock::now()); + case 15: return func(process_cpu_clock::now()); + } + return getErr(ErrCode::_EINVAL); +} + +int Timer::getTimeRes(SceKernelClockid id, SceKernelTimespec* tp) { + if (tp == nullptr) { + return getErr(ErrCode::_EFAULT); + } + + using namespace boost::chrono; + auto func = [tp](auto const& period) { + tp->tv_sec = static_cast(period.num / period.den); + tp->tv_nsec = static_cast((period.num % period.den) * 1e9 / period.den); + return Ok; + }; + + switch (id) { + case 0: + case 9: + case 10: return func(system_clock::period()); + case 4: + case 11: + case 12: return func(steady_clock::period()); + case 1: + case 2: + case 5: + case 7: + case 8: { + double const ts = 1.0 / accessTimer().getFrequency(); + time2timespec(tp, ts); + return Ok; + } + case 14: return func(thread_clock::period()); + case 15: return func(process_cpu_clock::period()); + } + return getErr(ErrCode::_EINVAL); +} + +int Timer::getTimeofDay(SceKernelTimeval* tp) { + if (tp == nullptr) { + return getErr(ErrCode::_EFAULT); + } + + using namespace boost::chrono; + uint64_t const t = time_point_cast(system_clock::now()).time_since_epoch().count(); + micro2timeval(tp, t); + return Ok; +} + +int Timer::getTimeZone(SceKernelTimezone* tz) { + if (tz == nullptr) return getErr(ErrCode::_EINVAL); + static bool isTZSet = false; + + if (!isTZSet) { + _tzset(); + isTZSet = true; + } + + long tz_secswest; + if (auto err = _get_timezone(&tz_secswest)) return getErr((ErrCode)err); + if (auto err = _get_daylight(&tz->tz_dsttime)) return getErr((ErrCode)err); + tz->tz_minuteswest = int(tz_secswest / 60); + + return Ok; +} diff --git a/core/timer/timer.h b/core/timer/timer.h new file mode 100644 index 00000000..3c59a4bd --- /dev/null +++ b/core/timer/timer.h @@ -0,0 +1,114 @@ +#pragma once +#include "modules_include/common.h" +#include "utility/utility.h" + +class ITimer { + CLASS_NO_COPY(ITimer); + + protected: + ITimer() = default; + + public: + /** + * @brief init the timer. Sets start time for relative time + * + */ + virtual void init() = 0; + + /** + * @brief Get the relative ticks + * + * @return uint64_t + */ + virtual uint64_t getTicks() = 0; + + /** + * @brief Get the relative Time in secounds + * + * @return double + */ + virtual double getTimeS() = 0; + + /** + * @brief Get the relative Time in ms + * + * @return double + */ + virtual double getTimeMs() = 0; + + /** + * @brief Get the absolute ticks + * + * @return uint64_t + */ + virtual uint64_t queryPerformance() = 0; + + /** + * @brief Pauses the relative timer + * + */ + virtual void pause() = 0; + + /** + * @brief Resumes the relative timer + * + */ + virtual void resume() = 0; + + /** + * @brief Get the Frequency of the ticks + * + * @return uint64_t + */ + virtual uint64_t getFrequency() = 0; + + /** + * @brief Check if paused + * + * @return true + * @return false + */ + virtual bool getPaused() = 0; + + /** + * @brief Get the Time from clockId + * + * @param id + * @param tp + * @return int getErr() + */ + virtual int getTime(SceKernelClockid id, SceKernelTimespec* tp) = 0; + + /** + * @brief Get the Time Resolution from clockId + * + * @param id + * @param tp + * @return int getErr() + */ + virtual int getTimeRes(SceKernelClockid id, SceKernelTimespec* tp) = 0; + + /** + * @brief Get the timeOfDay + * + * @param tp + * @return int getErr() + */ + virtual int getTimeofDay(SceKernelTimeval* tp) = 0; + + /** + * @brief Get the time zone info + * + */ + virtual int getTimeZone(SceKernelTimezone* tz) = 0; +}; + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif +__APICALL ITimer& accessTimer(); +#undef __APICALL diff --git a/core/trophies/CMakeLists.txt b/core/trophies/CMakeLists.txt new file mode 100644 index 00000000..d618553a --- /dev/null +++ b/core/trophies/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(trophies OBJECT + trophies.cpp +) + +add_dependencies(trophies third_party) + +file(COPY trophies.h DESTINATION ${DST_INCLUDE_DIR}/trophies) \ No newline at end of file diff --git a/core/trophies/trophies.cpp b/core/trophies/trophies.cpp new file mode 100644 index 00000000..f0a80de9 --- /dev/null +++ b/core/trophies/trophies.cpp @@ -0,0 +1,786 @@ +#define __APICALL_EXTERN +#include "trophies.h" +#undef __APICALL_EXTERN + +#include "core/fileManager/fileManager.h" +#include "modules/libSceNpTrophy/types.h" +#include "modules_include/system_param.h" +#include "tools/config_emu/config_emu.h" + +#include +#include +#include +#include + +#define TR_AES_BLOCK_SIZE 16 +#undef min // We don't need it there + +class Trophies: public ITrophies { + enum class cbtype { + UNKNOWN, + TROPHY_UNLOCK, + }; + + struct callback { + cbtype type; + vvpfunc func; + }; + + bool m_bKeySet = false; + bool m_bIgnoreMissingLocale = false; + uint8_t m_trkey[TR_AES_BLOCK_SIZE] = {}; + std::string m_localizedTrophyFile; + std::filesystem::path m_npidCachePath; + std::vector m_callbacks = {}; + std::mutex m_mutexParse; + + private: + std::vector m_ctx {}; + + struct trp_header { + uint32_t magic; // should be 0xDCA24D00 + uint32_t version; + uint64_t tfile_size; // size of trp file + uint32_t entry_num; // num of entries + uint32_t entry_size; // size of entry + uint32_t dev_flag; // 1: dev + uint8_t digest[20]; // sha hash + uint32_t key_index; + char padding[44]; + }; + + struct trp_entry { + char name[32]; + uint64_t pos; + uint64_t len; + uint32_t flag; + char padding[12]; + }; + + static bool caseequal(char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); } + + static ParserErr XML_parse(const char* mem, trp_context* ctx) { + pugi::xml_document doc; + + auto res = doc.load_buffer(mem, strlen(mem)); + + if (res.status != pugi::xml_parse_status::status_ok) return ParserErr::INVALID_XML; + + if (auto tc = doc.child("trophyconf")) { + if (!ctx->itrop.cancelled) { + ctx->itrop.data.trophyset_version.assign(tc.child("trophyset-version").first_child().text().as_string("[nover]")); + ctx->itrop.data.title_name.assign(tc.child("title-name").first_child().text().as_string("[unnamed]")); + ctx->itrop.data.title_detail.assign(tc.child("title-detail").first_child().text().as_string("[unnamed]")); + ctx->itrop.data.trophy_count = ctx->itrop.data.group_count = 0; + } + + for (auto node = tc.child("trophy"); node; node = node.next_sibling("trophy")) { + if (!ctx->entry.cancelled) { + ctx->entry.data.id = node.attribute("id").as_int(-1); + ctx->entry.data.group = node.attribute("gid").as_int(-1); + ctx->entry.data.platinum = node.attribute("pid").as_int(-1); + ctx->entry.data.hidden = node.attribute("hidden").as_bool(false); + ctx->entry.data.grade = std::tolower(*(node.attribute("ttype").as_string())); + + if (!ctx->lightweight) { + ctx->entry.data.name.assign(node.child("name").first_child().text().as_string("[unnamed]")); + ctx->entry.data.detail.assign(node.child("detail").first_child().text().as_string("[unnamed]")); + } + + ctx->entry.cancelled = ctx->entry.func(&ctx->entry.data); + } + + if (!ctx->itrop.cancelled) ++ctx->itrop.data.trophy_count; + } + + for (auto node = tc.child("group"); node; node = node.next_sibling("group")) { + if (!ctx->group.cancelled) { + ctx->group.data.id = node.attribute("id").as_int(-1); + + if (!ctx->lightweight) { + ctx->group.data.name.assign(node.child("name").first_child().text().as_string("[unnamed]")); + ctx->group.data.detail.assign(node.child("detail").first_child().text().as_string("[unnamed]")); + } + + ctx->group.cancelled = ctx->group.func(&ctx->group.data); + } + + if (!ctx->itrop.cancelled) ++ctx->itrop.data.group_count; + } + + if (!ctx->itrop.cancelled) ctx->itrop.func(&ctx->itrop.data); + + ctx->entry.cancelled = true; + ctx->group.cancelled = true; + ctx->itrop.cancelled = true; + return ParserErr::CONTINUE; + } + + return ParserErr::NO_TROPHIES; + } + + ParserErr TRP_readentry(const trp_entry& ent, trp_entry& dent, std::ifstream& trfile, trp_context* ctx, uint32_t label) { + if (!ctx->pngim.cancelled) { + static std::string_view ext(".png"); + std::string_view name(ent.name); + if (std::equal(ext.rbegin(), ext.rend(), name.rbegin(), caseequal)) { // Test trp file extension + if (((ent.flag >> 24) & 0x03) == 0) { + ctx->pngim.data.pngsize = ent.len; + ctx->pngim.data.pngname.assign(ent.name); + if ((ctx->pngim.data.pngdata = ::calloc(ent.len, 1)) == nullptr) { // Developer should free this memory manually + return ParserErr::OUT_OF_MEMORY; + } + trfile.seekg(ent.pos); + if (trfile.read((char*)ctx->pngim.data.pngdata, ent.len)) { + ctx->pngim.cancelled = ctx->pngim.func(&ctx->pngim.data); + return ParserErr::CONTINUE; + } + + return ParserErr::IO_FAIL; + } else { + // Is this even possible? + return ParserErr::NOT_IMPLEMENTED; + } + } + } + + if (!ctx->group.cancelled || !ctx->entry.cancelled || !ctx->itrop.cancelled) { + static std::string_view ext(".esfm"); + std::string_view name(ent.name); + if (!std::equal(ext.rbegin(), ext.rend(), name.rbegin(), caseequal)) return ParserErr::CONTINUE; + if ((ent.len % 16) != 0) return ParserErr::INVALID_AES; + if (ctx->lightweight) { + static std::string_view lwfile("tropconf.esfm"); + if (!std::equal(lwfile.begin(), lwfile.end(), name.begin(), name.end(), caseequal)) return ParserErr::CONTINUE; + } else if (!m_bIgnoreMissingLocale) { + static std::string_view dfile("trop.esfm"); + // Trying to find localized trophy + if (!std::equal(name.begin(), name.end(), m_localizedTrophyFile.begin(), m_localizedTrophyFile.end(), caseequal)) { + // Save the default one to `dent` variable. It will be used if no localized trophy configuration file found + if (std::equal(name.begin(), name.end(), dfile.begin(), dfile.end(), caseequal)) dent = ent; + return ParserErr::CONTINUE; + } + } + + boost::scoped_ptr mem(new char[ent.len]); + trfile.seekg(ent.pos); // Seek to file position + + if (((ent.flag >> 24) & 0x03) == 0) { + if (!trfile.read(mem.get(), ent.len)) return ParserErr::IO_FAIL; + } else { + static constexpr int32_t IV_SIZE = TR_AES_BLOCK_SIZE; + static constexpr int32_t ENC_SCE_SIGN_SIZE = TR_AES_BLOCK_SIZE * 3; // 384 encrypted bits is just enough to find interesting for us string + + uint8_t d_iv[TR_AES_BLOCK_SIZE]; + uint8_t kg_iv[TR_AES_BLOCK_SIZE]; + uint8_t enc_xmlh[ENC_SCE_SIGN_SIZE]; + ::memset(kg_iv, 0, TR_AES_BLOCK_SIZE); + + if (!trfile.read((char*)d_iv, TR_AES_BLOCK_SIZE)) return ParserErr::IO_FAIL; + if (!trfile.read((char*)enc_xmlh, ENC_SCE_SIGN_SIZE)) return ParserErr::IO_FAIL; + + const auto trydecrypt = [this, &mem, &ent, d_iv, kg_iv, enc_xmlh, &trfile](uint32_t npid) -> bool { + uint8_t outbuffer[512]; + uint8_t inbuffer[512]; + + ::memset(outbuffer, 0, 512); + ::memset(inbuffer, 0, 512); + ::sprintf_s((char*)inbuffer, sizeof(inbuffer), "NPWR%05d_00", npid); + + int outlen; + + // Key creation context + { + EVP_CIPHER_CTX* key_ctx = EVP_CIPHER_CTX_new(); + if (!EVP_EncryptInit(key_ctx, EVP_aes_128_cbc(), m_trkey, kg_iv)) { + EVP_CIPHER_CTX_free(key_ctx); + return false; + } + if (!EVP_EncryptUpdate(key_ctx, outbuffer, &outlen, inbuffer, TR_AES_BLOCK_SIZE)) { + EVP_CIPHER_CTX_free(key_ctx); + return false; + } + /** + * Cipher finalizing is not really necessary there, + * since we use only 16 bytes encrypted by the update function above + */ + EVP_CIPHER_CTX_free(key_ctx); + } + //- Key creation context + + // Data decipher context + EVP_CIPHER_CTX* data_ctx = EVP_CIPHER_CTX_new(); + { + if (!EVP_DecryptInit(data_ctx, EVP_aes_128_cbc(), outbuffer /* the buffer holds the decryption key now */, d_iv)) { + EVP_CIPHER_CTX_free(data_ctx); + return false; + } + + EVP_CIPHER_CTX_set_padding(data_ctx, 0); + + if (!EVP_DecryptUpdate(data_ctx, outbuffer, &outlen, enc_xmlh, ENC_SCE_SIGN_SIZE)) { + EVP_CIPHER_CTX_free(data_ctx); + return false; + } + if (::_strnicmp((char*)outbuffer, " VideoOut Open(%S)| %d:%d", title.c_str(), window.config.resolution.paneWidth, window.config.resolution.paneHeight); + if (m_vulkanObj == nullptr) { + m_vulkanObj = vulkan::initVulkan(window.window, window.surface, accessInitParams()->enableValidation()); + + m_graphics = createGraphics(*this, m_vulkanObj->deviceInfo); + + auto queue = m_vulkanObj->queues.items[getIndex(vulkan::QueueType::graphics)][0].get(); // todo use getQeueu + + m_useVsync = accessInitParams()->useVSYNC(); + + m_imageHandler = createImageHandler(m_vulkanObj->deviceInfo, VkExtent2D {window.config.resolution.paneWidth, window.config.resolution.paneHeight}, + queue, &window); + m_imageHandler->init(m_vulkanObj, window.surface); + + auto [format, _] = vulkan::getDisplayFormat(m_vulkanObj); + m_overlayHandler = createOverlay(m_vulkanObj->deviceInfo, m_imageHandler, window.window, queue, format); + + *item.result = 0; + } else { + vulkan::createSurface(m_vulkanObj, window.window, window.surface); + } + + m_condDone.notify_one(); + } break; + case MessageType::close: { + SDL_DestroyWindow(window.window); + *item.result = 0; + m_condDone.notify_one(); + } break; + case MessageType::flip: { + LOG_DEBUG(L"-> flip(%d) set:%u buffer:%u", item.index, item.setIndex, item.imageData.index); + OPTICK_FRAME("VideoOut"); + auto& flipStatus = window.config.flipStatus; + + { + lock.unlock(); + OPTICK_EVENT("Present"); + presentImage(item.imageData, m_imageHandler->getSwapchain(), m_imageHandler->getQueue()); + m_imageHandler->notify_done(item.imageData); + lock.lock(); + } + m_graphics->submitDone(); + m_imageHandler->calc_fps(flipStatus.processTime); + + doFlip(window, 1 + handleIndex); + + window.config.fps = m_imageHandler->getFPS(); + auto title = getTitle(1 + handleIndex, flipStatus.count, round(window.config.fps), window.fliprate); + + SDL_SetWindowTitle(window.window, title.c_str()); + func_pollSDL(window.window); + + LOG_DEBUG(L"<- flip(%d) set:%u buffer:%u", handleIndex, item.setIndex, item.imageData.index); + } break; + } + m_messages.pop(); + } + SDL_Quit(); + }); +} diff --git a/core/videoout/videoout.h b/core/videoout/videoout.h new file mode 100644 index 00000000..5aa8f4d1 --- /dev/null +++ b/core/videoout/videoout.h @@ -0,0 +1,224 @@ +#pragma once + +#include "core/kernel/eventqueue_types.h" + +#include +#include + +namespace vulkan { +struct SwapchainData; +} // namespace vulkan + +constexpr int VIDEO_OUT_EVENT_FLIP = 0; +constexpr int VIDEO_OUT_EVENT_VBLANK = 1; + +class IGraphics; +union SDL_Event; +struct SDL_Window; + +typedef void (*SDLEventFunc)(SDL_Event*, void*); + +class IVideoOut { + CLASS_NO_COPY(IVideoOut); + CLASS_NO_MOVE(IVideoOut); + + protected: + IVideoOut() = default; + + public: + virtual ~IVideoOut() = default; + + /** + * @brief Open a window + * + * @param userId + * @return int internal handle of the window + */ + virtual int open(int userId) = 0; + + /** + * @brief Closes the window + * + * @param handle + */ + virtual void close(int handle) = 0; + + /** + * @brief Set the fps to use + * + * @param handle + * @param rate + */ + virtual void setFliprate(int handle, int rate) = 0; + + /** + * @brief Returns the current display's safe area + * + * @param area + */ + virtual void getSafeAreaRatio(float* area) = 0; + + /** + * @brief Add a VIDEO_OUT_EVENT for the window + * + * @param handle + * @param event + * @param eq + * @return int + */ + virtual int addEvent(int handle, Kernel::EventQueue::KernelEqueueEvent const& event, Kernel::EventQueue::IKernelEqueue_t eq) = 0; + + /** + * @brief Removes a VIDEO_OUT_EVENT for the window + * + * @param handle + * @param eq + * @param ident + */ + virtual void removeEvent(int handle, Kernel::EventQueue::IKernelEqueue_t eq, int const ident) = 0; + + /** + * @brief Submit flip video buffers to the queue + * + * @param index + * @param flipArg used by the flip event + */ + virtual void submitFlip(int handle, int index, int64_t flipArg) = 0; + + /** + * @brief Get the Flip Status + * + * @param handle + * @param status + */ + virtual void getFlipStatus(int handle, void* status) = 0; + + /** + * @brief Get the VBlank Status (currently faked by a timer) + * + * @param handle + * @param status + */ + virtual void getVBlankStatus(int handle, void* status) = 0; + + /** + * @brief Get the current resolution + * + * @param handle + * @param status + */ + virtual void getResolution(int handle, void* status) = 0; + + /** + * @brief Get the number of unhandled flip submits + * + * @param handle + * @return int + */ + virtual int getPendingFlips(int handle) = 0; + + /** + * @brief Get the video Buffer Attributes + * + * @param attribute + * @param pixel_format + * @param tiling_mode + * @param aspect_ratio + * @param width + * @param height + * @param pitch_in_pixel + */ + virtual void getBufferAttribute(void* attribute, uint32_t pixel_format, int32_t tiling_mode, int32_t aspect_ratio, uint32_t width, uint32_t height, + uint32_t pitch_in_pixel) = 0; + + /** + * @brief Registers a video buffer + * + * @param handle + * @param startIndex + * @param addresses + * @param numBuffer + * @param attribute + * @return int + */ + virtual int registerBuffers(int handle, int startIndex, void* const* addresses, int numBuffer, const void* attribute) = 0; + + /** + * @brief Access the graphics interface + * + * @return IGraphics* + */ + virtual IGraphics* getGraphics() = 0; + + /** + * @brief calls SDL_InitSubSystem() on correct thread context + * + * @param flags + * @return 0: success + */ + virtual int SDLInit(uint32_t flags) = 0; + + /** + * @brief Register SDL event listener + * + * @param type + * @param eventFunc + * @return int + */ + virtual void SDLEventReg(uint32_t type, SDLEventFunc eventFunc, void* userData) = 0; + + /** + * @brief Unrgister SDL event listener by type and function pointer + * + * @param type + * @param eventFunc + * @return int + */ + virtual bool SDLEventUnreg(uint32_t type, SDLEventFunc eventFunc) = 0; + + /** + * @brief Unrgister all the SDL event listeners by function pointer + * + * @param eventFunc + * @return int + */ + virtual bool SDLEventUnreg(SDLEventFunc eventFunc) = 0; + + /** + * @brief Returns the main SDL Window handle + * + * @return SDL_Window* + */ + virtual SDL_Window* SDLWindow() = 0; + + /** + * @brief Notify a gpu visible memory range + * + * @return true: Memory has been allocated successfully + */ + virtual bool notify_allocHeap(uint64_t vaddr, uint64_t size, int memoryProtection) = 0; + + /** + * @brief Checks if the vaddr is gpu memory (prev notify_allocHeap) + * + * @param vaddr + * @return true is gpu local memory + * @return false + */ + virtual bool isGPULocal(uint64_t vaddr) = 0; +}; + +#ifndef PSOFF_RENDER_VERSION +#define PSOFF_RENDER_VERSION "unknown" +#endif + +#if defined(__APICALL_EXTERN) +#define __APICALL __declspec(dllexport) +#elif defined(__APICALL_IMPORT) +#define __APICALL __declspec(dllimport) +#else +#define __APICALL +#endif +__APICALL IVideoOut& accessVideoOut(); + +#undef __APICALL diff --git a/core/videoout/vulkan/vulkanHelper.cpp b/core/videoout/vulkan/vulkanHelper.cpp new file mode 100644 index 00000000..02f4103d --- /dev/null +++ b/core/videoout/vulkan/vulkanHelper.cpp @@ -0,0 +1,186 @@ +#include "vulkanHelper.h" + +#include "../imageHandler.h" +#include "core/initParams/initParams.h" +#include "logging.h" +#include "utility/utility.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_DEFINE_MODULE(vulkanHelper); + +namespace vulkan { + +void createSurface(VulkanObj* obj, SDL_Window* window, VkSurfaceKHR& surfaceOut) { + LOG_USE_MODULE(vulkanHelper); + if (SDL_Vulkan_CreateSurface(window, obj->deviceInfo->instance, &surfaceOut)) { + LOG_CRIT(L"Couldn't create surface"); + } +} + +std::pair getDisplayFormat(VulkanObj* obj) { + if (obj->surfaceCapabilities.formats.empty()) { + return {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; + } + + std::vector> formatsSrgb; + std::vector> formatsUnorm; + + formatsSrgb.reserve(obj->surfaceCapabilities.formats.size()); + formatsUnorm.reserve(obj->surfaceCapabilities.formats.size()); + + for (auto const& format: obj->surfaceCapabilities.formats) { + switch (format.format) { + case VK_FORMAT_B8G8R8A8_SRGB: formatsSrgb.push_back({1, VK_FORMAT_B8G8R8A8_SRGB}); break; + case VK_FORMAT_R8G8B8A8_SRGB: formatsSrgb.push_back({2, VK_FORMAT_R8G8B8A8_SRGB}); break; + case VK_FORMAT_B8G8R8A8_UNORM: formatsUnorm.push_back({1, VK_FORMAT_B8G8R8A8_UNORM}); break; + case VK_FORMAT_B8G8R8A8_SNORM: formatsUnorm.push_back({2, VK_FORMAT_B8G8R8A8_SNORM}); break; + default: break; + } + } + + sort(formatsSrgb.begin(), formatsSrgb.end()); + sort(formatsUnorm.begin(), formatsUnorm.end()); + + if (accessInitParams()->enableBrightness()) { + if (formatsSrgb.empty()) { + LOG_USE_MODULE(vulkanHelper); + for (auto const& format: obj->surfaceCapabilities.formats) { + LOG_ERR(L"format %S", string_VkFormat(format.format)); + } + LOG_CRIT(L"VK_FORMAT_B8G8R8A8_SRGB not supported"); + } + return {formatsSrgb[0].second, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; + } + + if (formatsUnorm.empty()) { + LOG_USE_MODULE(vulkanHelper); + for (auto const& format: obj->surfaceCapabilities.formats) { + LOG_ERR(L"format %S", string_VkFormat(format.format)); + } + LOG_CRIT(L"VK_FORMAT_B8G8R8A8_UNORM not supported"); + } + return {formatsUnorm[0].second, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; +} + +void submitDisplayTransfer(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, QueueInfo const* queue, VkSemaphore waitSema, + size_t waitValue) { + LOG_USE_MODULE(vulkanHelper); + + if (vkEndCommandBuffer(imageData.cmdBuffer) != VK_SUCCESS) { + LOG_CRIT(L"Couldn't end commandbuffer"); + } + + size_t waitValues[] = {0, waitValue}; + uint32_t waitCount = waitSema != nullptr ? 2 : 1; + + VkTimelineSemaphoreSubmitInfo const timelineInfo { + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + .waitSemaphoreValueCount = waitCount, + .pWaitSemaphoreValues = waitValues, + }; + + VkPipelineStageFlags waitStage[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT}; + VkSemaphore sems[] = {imageData.semImageReady, waitSema}; + + VkSubmitInfo const submitInfo { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = waitSema != nullptr ? &timelineInfo : nullptr, + + .waitSemaphoreCount = waitCount, + .pWaitSemaphores = sems, + .pWaitDstStageMask = waitStage, + + .commandBufferCount = 1, + .pCommandBuffers = &imageData.cmdBuffer, + + .signalSemaphoreCount = 1, + .pSignalSemaphores = &imageData.semImageCopied, + }; + + { + std::unique_lock lock(queue->mutex); + if (VkResult result = vkQueueSubmit(queue->queue, 1, &submitInfo, imageData.submitFence); result != VK_SUCCESS) { + LOG_CRIT(L"Couldn't vkQueueSubmit Transfer %S", string_VkResult(result)); + } + } +} + +void transfer2Display(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, IGraphics* graphics) { + LOG_USE_MODULE(vulkanHelper); + + { + VkImageMemoryBarrier const barrier { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + + .image = imageData.swapchainImage, + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; + + vkCmdPipelineBarrier(imageData.cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + + graphics->copyDisplayBuffer(displayBuffer->bufferVaddr, imageData.cmdBuffer, imageData.swapchainImage, imageData.extent); // let gpumemorymanager decide + + { + // Change to Present Layout + VkImageMemoryBarrier const barrier { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + + .image = imageData.swapchainImage, + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; + + vkCmdPipelineBarrier(imageData.cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + // - Present layout +} + +void presentImage(ImageData const& imageData, VkSwapchainKHR swapchain, QueueInfo const* queue) { + LOG_USE_MODULE(vulkanHelper); + + VkPresentInfoKHR const presentInfo { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = nullptr, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &imageData.semImageCopied, + .swapchainCount = 1, + .pSwapchains = &swapchain, + .pImageIndices = &imageData.index, + .pResults = nullptr, + }; + + { + OPTICK_GPU_FLIP(&swapchain); + OPTICK_CATEGORY("Present", Optick::Category::Wait); + + std::unique_lock lock(queue->mutex); + vkQueuePresentKHR(queue->queue, &presentInfo); + } +} +} // namespace vulkan \ No newline at end of file diff --git a/core/videoout/vulkan/vulkanHelper.h b/core/videoout/vulkan/vulkanHelper.h new file mode 100644 index 00000000..54f60928 --- /dev/null +++ b/core/videoout/vulkan/vulkanHelper.h @@ -0,0 +1,27 @@ +#pragma once + +#include "vulkanSetup.h" + +#include + +class IGraphics; +struct ImageData; + +namespace vulkan { +struct PresentData { + VkImage swapchainImage = nullptr; + VkSemaphore displayReady = nullptr; + VkSemaphore presentReady = nullptr; + uint32_t index = 0; +}; + +std::pair getDisplayFormat(VulkanObj* obj); + +void submitDisplayTransfer(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, QueueInfo const* queue, VkSemaphore waitSema, + size_t waitValue); + +void transfer2Display(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, IGraphics* graphics); + +void presentImage(ImageData const& imageData, VkSwapchainKHR swapchain, QueueInfo const* queue); + +} // namespace vulkan diff --git a/core/videoout/vulkan/vulkanSetup.cpp b/core/videoout/vulkan/vulkanSetup.cpp new file mode 100644 index 00000000..b8226892 --- /dev/null +++ b/core/videoout/vulkan/vulkanSetup.cpp @@ -0,0 +1,817 @@ +#include "vulkanSetup.h" + +#include "logging.h" + +#include +#include +#include +#include +#include + +LOG_DEFINE_MODULE(vulkanSetup); + +constexpr std::array disabledValidationFeatures {}; + +constexpr std::array enabledValidationFeatures {VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT}; + +constexpr std::array requiredExtensions { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, VK_KHR_MAINTENANCE_2_EXTENSION_NAME, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, + VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, + VK_EXT_SEPARATE_STENCIL_USAGE_EXTENSION_NAME, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, + // VK_EXT_SHADER_OBJECT_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, "VK_KHR_external_memory_win32"}; + +namespace vulkan { +struct VulkanExtensions { + bool enableValidationLayers = true; + + std::list requiredExtensions; + + std::vector availableExtensions; + std::list requiredLayers; + std::vector availableLayers; +}; + +struct VulkanQueues { + struct QueueInfo { + uint32_t family = 0; + uint32_t index = 0; + uint32_t count = 0; + + bool graphics = false; + bool compute = false; + bool transfer = false; + bool present = false; + }; + + uint32_t familyCount = 0; + std::vector familyUsed; + std::vector graphics; + std::vector compute; + std::vector transfer; + std::vector present; +}; + +VKAPI_ATTR VkResult VKAPI_CALL createDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* createInfo, + const VkAllocationCallbacks* allocator, VkDebugUtilsMessengerEXT* messenger) { + + if (auto func = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); func != nullptr) { + return func(instance, createInfo, allocator, messenger); + } + return VK_ERROR_EXTENSION_NOT_PRESENT; +} + +VKAPI_ATTR VkBool32 VKAPI_CALL debugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* /*user_data*/) { + LOG_USE_MODULE(vulkanSetup); + + const char* severityStr = nullptr; + bool skip = false; + bool error = false; + bool debugPrintf = false; + + switch (message_severity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + severityStr = "V"; + skip = true; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + if ((messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) != 0 && strcmp(callback_data->pMessageIdName, "UNASSIGNED-DEBUG-PRINTF") == 0) { + debugPrintf = true; + skip = true; + } else { + severityStr = "I"; + } + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: severityStr = "W"; break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + severityStr = "E"; + error = true; + break; + default: severityStr = "?"; + } + + if (error) { + LOG_WARN(L"[Vulkan][%S][%u]: %S", severityStr, static_cast(messageTypes), callback_data->pMessage); + } else if (!skip) { + LOG_DEBUG(L"[Vulkan][%S][%u]: %S", severityStr, static_cast(messageTypes), callback_data->pMessage); + } + + if (debugPrintf) { + auto strs = util::splitString(std::string(callback_data->pMessage), U'|'); + if (!strs.empty()) { + LOG_DEBUG(L"%S", std::string(strs.back()).data()); + } + } + + return VK_FALSE; +} + +VulkanExtensions getExtensions(SDL_Window* window, bool enableValidation) { + LOG_USE_MODULE(vulkanSetup); + + uint32_t countAvailableExtensions = 0; + uint32_t countAvailableLayers = 0; + uint32_t countRequiredExtensions = 0; + + VulkanExtensions r = {.enableValidationLayers = enableValidation}; + + SDL_Vulkan_GetInstanceExtensions(window, &countRequiredExtensions, NULL); + + auto extensions = static_cast(SDL_malloc(sizeof(char*) * countRequiredExtensions)); + + SDL_Vulkan_GetInstanceExtensions(window, &countRequiredExtensions, extensions); + for (size_t n = 0; n < countRequiredExtensions; n++) { + r.requiredExtensions.push_back(extensions[n]); + } + SDL_free(extensions); + + vkEnumerateInstanceExtensionProperties(nullptr, &countAvailableExtensions, nullptr); + r.availableExtensions = std::vector(countAvailableExtensions, VkExtensionProperties {}); + vkEnumerateInstanceExtensionProperties(nullptr, &countAvailableExtensions, r.availableExtensions.data()); + + if (std::find_if(r.availableExtensions.begin(), r.availableExtensions.end(), + [](VkExtensionProperties s) { return strcmp(s.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0; }) != r.availableExtensions.end()) { + r.requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } else { + r.enableValidationLayers = false; + } + + for (auto& ext: r.requiredExtensions) { + LOG_INFO(L"SDL2 required extension: %S", ext.c_str()); + } + + for (const auto& ext: r.availableExtensions) { + LOG_DEBUG(L"Vulkan available extension: %S specVer:%u", ext.extensionName, ext.specVersion); + } + + vkEnumerateInstanceLayerProperties(&countAvailableLayers, nullptr); + r.availableLayers = std::vector(countAvailableLayers, VkLayerProperties {}); + vkEnumerateInstanceLayerProperties(&countAvailableLayers, r.availableLayers.data()); + + for (const auto& l: r.availableLayers) { + LOG_DEBUG(L"Vulkan available layer:%S specVer:%u.%u implVer:%u (%S)", l.layerName, VK_API_VERSION_MAJOR(l.specVersion), VK_API_VERSION_MINOR(l.specVersion), + l.implementationVersion, l.description); + } + + if (r.enableValidationLayers) { + r.requiredLayers.push_back("VK_LAYER_KHRONOS_validation"); + for (auto const l: r.requiredLayers) { + if (std::find_if(r.availableLayers.begin(), r.availableLayers.end(), [&l](auto s) { return strcmp(s.layerName, l.c_str()) == 0; }) == + r.availableLayers.end()) { + LOG_INFO(L"no validation layer:%S", l.c_str()); + r.enableValidationLayers = false; + break; + } + } + } + + if (r.enableValidationLayers) { + vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &countAvailableExtensions, nullptr); + std::vector availableExtensions(countAvailableExtensions, VkExtensionProperties {}); + vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &countAvailableExtensions, availableExtensions.data()); + + for (const auto& ext: availableExtensions) { + LOG_DEBUG(L"VK_LAYER_KHRONOS_validation available extension: %S version:%u", ext.extensionName, ext.specVersion); + } + + if (std::find_if(availableExtensions.begin(), availableExtensions.end(), + [](auto s) { return strcmp(s.extensionName, "VK_EXT_validation_features") == 0; }) != availableExtensions.end()) { + r.requiredExtensions.push_back("VK_EXT_validation_features"); + } else { + r.enableValidationLayers = false; + } + } + + return r; +} + +VulkanQueues findQueues(VkPhysicalDevice device, VkSurfaceKHR surface) { + LOG_USE_MODULE(vulkanSetup); + VulkanQueues qs; + + vkGetPhysicalDeviceQueueFamilyProperties(device, &qs.familyCount, nullptr); + std::vector queueFamilies(qs.familyCount, VkQueueFamilyProperties {}); + vkGetPhysicalDeviceQueueFamilyProperties(device, &qs.familyCount, queueFamilies.data()); + + std::vector queueInfos; + qs.familyUsed.resize(queueFamilies.size()); + + for (uint32_t n = 0; n < queueFamilies.size(); ++n) { + VkBool32 presentationSupported = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(device, n, surface, &presentationSupported); + + auto const& queue = queueFamilies[n]; + LOG_DEBUG(L"queue family[%u]: %S [count:%u] [present=%S]", n, string_VkQueueFlags(queue.queueFlags).c_str(), queue.queueCount, + util::getBoolStr(presentationSupported == VK_TRUE)); + + VulkanQueues::QueueInfo info { + .family = n, + .count = queue.queueCount, + .graphics = (queue.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0, + .compute = (queue.queueFlags & VK_QUEUE_COMPUTE_BIT) != 0, + .transfer = (queue.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0, + .present = (bool)presentationSupported, + }; + + queueInfos.push_back(info); + } + + // Prio + auto gather = [&queueInfos, &qs](uint32_t count, std::vector& queue, auto const cmp) { + for (uint32_t i = queue.size(); i < count; i++) { + if (auto it = std::find_if(queueInfos.begin(), queueInfos.end(), cmp); it != queueInfos.end()) { + it->index = 0; + + auto& item = queue.emplace_back(*it); + if (it->count > qs.familyUsed[it->family]) { + item.index = qs.familyUsed[it->family]; + qs.familyUsed[it->family]++; + } + + } else { + break; + } + } + }; + + gather(1, qs.graphics, [](VulkanQueues::QueueInfo& q) { return q.graphics && q.transfer && q.present; }); + gather(1, qs.present, [](VulkanQueues::QueueInfo& q) { return q.graphics && q.transfer && q.present; }); + gather(1, qs.transfer, [](VulkanQueues::QueueInfo& q) { return q.transfer && !q.graphics && !q.compute; }); + gather(1, qs.compute, [](VulkanQueues::QueueInfo& q) { return q.compute && !q.graphics; }); + //- + + // Fill in whatever is left + gather(1, qs.graphics, [](VulkanQueues::QueueInfo& q) { return q.transfer == true; }); + gather(1, qs.present, [](VulkanQueues::QueueInfo& q) { return q.present == true; }); + gather(1, qs.transfer, [](VulkanQueues::QueueInfo& q) { return q.graphics == true; }); + gather(1, qs.compute, [](VulkanQueues::QueueInfo& q) { return q.compute == true; }); + // + + //- + + return qs; +} + +void dumpQueues(const VulkanQueues& qs) { + LOG_USE_MODULE(vulkanSetup); + + std::string familyString; + for (auto u: qs.familyUsed) { + familyString += std::to_string(u) + std::string(", "); + } + LOG_INFO(L"\t familyUsed = [%S]", familyString.c_str()); + + LOG_INFO(L"\t graphics:"); + for (const auto& q: qs.graphics) { + LOG_INFO(L"\t\t family:%u index:%u", q.family, q.index); + } + + LOG_INFO(L"\t compute:"); + for (const auto& q: qs.compute) { + LOG_INFO(L"\t\t family:%u index:%u", q.family, q.index); + } + + LOG_INFO(L"\t transfer:"); + for (const auto& q: qs.transfer) { + LOG_INFO(L"\t\t family:%u, index:%u", q.family, q.index); + } + LOG_INFO(L"\t present:"); + for (const auto& q: qs.present) { + LOG_INFO(L"\t\t family:%u, index:%u", q.family, q.index); + } +} + +void getSurfaceCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, SurfaceCapabilities& r) { + LOG_USE_MODULE(vulkanSetup); + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &r.capabilities); + + uint32_t formats_count = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formats_count, nullptr); + + r.formats = std::vector(formats_count, VkSurfaceFormatKHR {}); + vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formats_count, r.formats.data()); + + uint32_t present_modes_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &present_modes_count, nullptr); + + r.presentModes = std::vector(present_modes_count, VkPresentModeKHR {}); + vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &present_modes_count, r.presentModes.data()); + + r.format_srgb_bgra32 = false; + for (const auto& f: r.formats) { + if (f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + r.format_srgb_bgra32 = true; + break; + } + if (f.format == VK_FORMAT_B8G8R8A8_UNORM && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + r.format_unorm_bgra32 = true; + break; + } + } +} + +bool checkFormat(VkPhysicalDevice device, VkFormat format, VkFormatFeatureFlags features) { + VkFormatProperties formatProps; + vkGetPhysicalDeviceFormatProperties(device, format, &formatProps); + + if ((formatProps.optimalTilingFeatures & features) == features || (formatProps.linearTilingFeatures & features) == features) { + return true; + } + return false; +} + +void findPhysicalDevice(VkInstance instance, VkSurfaceKHR surface, SurfaceCapabilities* outCapabilities, VkPhysicalDevice* outDevice, VulkanQueues* outQueues, + bool enableValidation) { + LOG_USE_MODULE(vulkanSetup); + + uint32_t devicesCount = 0; + vkEnumeratePhysicalDevices(instance, &devicesCount, nullptr); + + if (devicesCount == 0) { + LOG_CRIT(L"No GPUs found"); + } + + std::vector devices(devicesCount); + vkEnumeratePhysicalDevices(instance, &devicesCount, devices.data()); + + // Find Best + VkPhysicalDevice bestDevice = nullptr; + VulkanQueues bestQueues; + + bool skipDevice = false; + for (const auto& device: devices) { + skipDevice = false; + + VkPhysicalDeviceInlineUniformBlockFeaturesEXT uniBlock { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES, + .pNext = nullptr, + }; + + VkPhysicalDeviceExtendedDynamicState3FeaturesEXT extendedDynamic3Features { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT, + .pNext = &uniBlock, + }; + + VkPhysicalDeviceDepthClipControlFeaturesEXT depthClipControlFeatures { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT, + .pNext = &extendedDynamic3Features, + .depthClipControl = VK_TRUE, + }; + + VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, + .pNext = &depthClipControlFeatures, + .dynamicRendering = VK_TRUE, + }; + + VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeature { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, + .pNext = &dynamicRenderingFeature, + .timelineSemaphore = VK_TRUE, + }; + + VkPhysicalDeviceBufferDeviceAddressFeatures bufferDeviceAddress {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, + .pNext = &timelineSemaphoreFeature}; + + VkPhysicalDeviceDescriptorIndexingFeatures descIndexing { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, + .pNext = &bufferDeviceAddress, + }; + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObj {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, .pNext = &descIndexing}; + + VkPhysicalDeviceDescriptorIndexingFeatures indexingFeature {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT, + .pNext = &shaderObj}; + + VkPhysicalDeviceProvokingVertexFeaturesEXT provVertex { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, + .pNext = &descIndexing, + }; + + VkPhysicalDeviceColorWriteEnableFeaturesEXT colorWriteExt { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT, + .pNext = &provVertex, + }; + + VkPhysicalDeviceFeatures2 deviceFeatures {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &colorWriteExt}; + + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(device, &deviceProperties); + vkGetPhysicalDeviceFeatures2(device, &deviceFeatures); + + LOG_INFO(L"Vulkan device: %S api:%u.%u:%u", deviceProperties.deviceName, VK_API_VERSION_MAJOR(deviceProperties.apiVersion), + VK_API_VERSION_MINOR(deviceProperties.apiVersion), VK_API_VERSION_PATCH(deviceProperties.apiVersion)); + + auto qs = findQueues(device, surface); + dumpQueues(qs); + + // if (shaderObj.shaderObject == VK_FALSE) { + // LOG_ERR(L"shaderObject is not supported"); + // skipDevice = true; + // } + + if (qs.graphics.empty() || qs.compute.empty() || qs.transfer.empty() || qs.present.empty()) { + LOG_ERR(L"Not enough queues"); + skipDevice = true; + } + + if (colorWriteExt.colorWriteEnable != VK_TRUE) { + LOG_ERR(L"colorWriteEnable is not supported"); + skipDevice = true; + } + + if (provVertex.provokingVertexLast != VK_TRUE) { + LOG_ERR(L"provokingVertexLast is not supported"); + skipDevice = true; + } + + if (deviceFeatures.features.fragmentStoresAndAtomics != VK_TRUE) { + LOG_ERR(L"fragmentStoresAndAtomics is not supported"); + skipDevice = true; + } + + if (deviceFeatures.features.samplerAnisotropy != VK_TRUE) { + LOG_ERR(L"samplerAnisotropy is not supported"); + skipDevice = true; + } + + if (!skipDevice) { + uint32_t numExt = 0; + vkEnumerateDeviceExtensionProperties(device, nullptr, &numExt, nullptr); + if (numExt == 0) { + LOG_CRIT(L"no extensions found"); + } + + std::vector availableExt(numExt, VkExtensionProperties {}); + vkEnumerateDeviceExtensionProperties(device, nullptr, &numExt, availableExt.data()); + + for (const char* ext: requiredExtensions) { + if (std::find_if(availableExt.begin(), availableExt.end(), [&ext](auto p) { return strcmp(p.extensionName, ext) == 0; }) == availableExt.end()) { + LOG_ERR(L"%S not supported", ext); + skipDevice = true; + break; + } + } + if (enableValidation) { + for (const auto& ext: availableExt) { + LOG_DEBUG(L"Vulkan available extension: %S, version:%u", ext.extensionName, ext.specVersion); + } + } + } + if (!skipDevice) { + getSurfaceCapabilities(device, surface, *outCapabilities); + + if ((outCapabilities->capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) { + LOG_DEBUG(L"Surface cannot be destination of blit"); + skipDevice = true; + } + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_BLIT_SRC_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as transfer source"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_D32_SFLOAT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_D32_SFLOAT cannot be used as depth buffer"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_D32_SFLOAT_S8_UINT cannot be used as depth buffer"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_D16_UNORM, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_D16_UNORM cannot be used as depth buffer"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_D24_UNORM_S8_UINT cannot be used as depth buffer"); + // skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_BC3_SRGB_BLOCK cannot be used as texture"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as texture"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_R8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_R8_UNORM cannot be used as texture"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_R8G8_UNORM cannot be used as texture"); + skipDevice = true; + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as texture"); + + if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_UNORM cannot be used as texture"); + skipDevice = true; + } + } + + if (!skipDevice && !checkFormat(device, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_B8G8R8A8_SRGB cannot be used as texture"); + + if (!skipDevice && !checkFormat(device, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) { + LOG_DEBUG(L"Format VK_FORMAT_B8G8R8A8_UNORM cannot be used as texture"); + skipDevice = true; + } + } + + if (!skipDevice && deviceProperties.limits.maxSamplerAnisotropy < 16.0f) { + LOG_DEBUG(L"maxSamplerAnisotropy < 16.0f"); + skipDevice = true; + } + + if (skipDevice) { + continue; + } + + if (bestDevice == nullptr || deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + bestDevice = device; + bestQueues = qs; + } + } + + // -Find best + + *outDevice = bestDevice; + *outQueues = bestQueues; +} + +VkDevice createDevice(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VulkanExtensions const extensions, VulkanQueues const& queues, + bool enableValidation) { + LOG_USE_MODULE(vulkanSetup); + std::vector queueCreateInfo(queues.familyCount); + std::vector> queuePrio(queues.familyCount); + + uint32_t numQueueCreateInfo = 0; + for (uint32_t i = 0; i < queues.familyCount; i++) { + if (queues.familyUsed[i] != 0) { + for (uint32_t pi = 0; pi < queues.familyUsed[i]; pi++) { + queuePrio[numQueueCreateInfo].push_back(1.0f); + } + + queueCreateInfo[numQueueCreateInfo].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo[numQueueCreateInfo].pNext = nullptr; + queueCreateInfo[numQueueCreateInfo].flags = 0; + queueCreateInfo[numQueueCreateInfo].queueFamilyIndex = i; + queueCreateInfo[numQueueCreateInfo].queueCount = queues.familyUsed[i]; + queueCreateInfo[numQueueCreateInfo].pQueuePriorities = queuePrio[numQueueCreateInfo].data(); + + numQueueCreateInfo++; + } + } + + VkPhysicalDeviceFeatures deviceFeatures { + .geometryShader = VK_TRUE, .fillModeNonSolid = VK_TRUE, .wideLines = VK_TRUE, .samplerAnisotropy = VK_TRUE, .fragmentStoresAndAtomics = VK_TRUE, + // deviceFeatures.shaderImageGatherExtended = VK_TRUE; + }; + + VkPhysicalDeviceInlineUniformBlockFeaturesEXT uniBlock { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES, .pNext = nullptr, + //.inlineUniformBlock = VK_TRUE, + //.descriptorBindingInlineUniformBlockUpdateAfterBind = VK_TRUE, + }; + + VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, .shaderObject = VK_TRUE}; + + VkPhysicalDeviceExtendedDynamicState3FeaturesEXT extendedDynamic3Features { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT, + .pNext = &uniBlock, + .extendedDynamicState3PolygonMode = VK_TRUE, + .extendedDynamicState3RasterizationSamples = VK_TRUE, + .extendedDynamicState3AlphaToCoverageEnable = VK_TRUE, + //.extendedDynamicState3AlphaToOneEnable = VK_TRUE, + .extendedDynamicState3ColorBlendEnable = VK_TRUE, + .extendedDynamicState3ColorBlendEquation = VK_TRUE, + .extendedDynamicState3ColorWriteMask = VK_TRUE, + .extendedDynamicState3DepthClipNegativeOneToOne = VK_TRUE, + }; + + VkPhysicalDeviceDepthClipControlFeaturesEXT depthClipControlFeatures { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT, + .pNext = &extendedDynamic3Features, + .depthClipControl = VK_TRUE, + }; + + VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, + .pNext = &depthClipControlFeatures, + .dynamicRendering = VK_TRUE, + }; + + VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeature { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, + .pNext = &dynamicRenderingFeature, + .timelineSemaphore = VK_TRUE, + }; + + VkPhysicalDeviceColorWriteEnableFeaturesEXT colorWriteExt { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT, + .pNext = &timelineSemaphoreFeature, + .colorWriteEnable = VK_TRUE, + }; + + VkPhysicalDeviceBufferDeviceAddressFeatures bufferDeviceAddress { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, + .pNext = &colorWriteExt, + .bufferDeviceAddress = VK_TRUE, + + .bufferDeviceAddressCaptureReplay = enableValidation ? VK_TRUE : VK_FALSE, + }; + + VkPhysicalDeviceDescriptorIndexingFeatures descIndexing { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, + .pNext = &bufferDeviceAddress, + .descriptorBindingUniformBufferUpdateAfterBind = VK_FALSE, // Todo: only optional! + .descriptorBindingSampledImageUpdateAfterBind = VK_TRUE, + .descriptorBindingStorageImageUpdateAfterBind = VK_TRUE, + .descriptorBindingStorageBufferUpdateAfterBind = VK_TRUE, + .descriptorBindingPartiallyBound = VK_TRUE, + .descriptorBindingVariableDescriptorCount = VK_TRUE, + .runtimeDescriptorArray = VK_TRUE, + }; + + VkPhysicalDeviceProvokingVertexFeaturesEXT provVertex { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT, + .pNext = &descIndexing, + .provokingVertexLast = VK_TRUE, + }; + + std::vector reqLayers; + reqLayers.reserve(1 + extensions.requiredLayers.size()); + + for (auto& req: extensions.requiredLayers) { + reqLayers.push_back(req.c_str()); + } + + VkDeviceCreateInfo const createInfo { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &provVertex, + .flags = 0, + .queueCreateInfoCount = numQueueCreateInfo, + .pQueueCreateInfos = queueCreateInfo.data(), + .enabledLayerCount = (uint32_t)reqLayers.size(), + .ppEnabledLayerNames = reqLayers.data(), + .enabledExtensionCount = requiredExtensions.size(), + .ppEnabledExtensionNames = requiredExtensions.data(), + .pEnabledFeatures = &deviceFeatures, + }; + + VkDevice device = nullptr; + if (auto result = vkCreateDevice(physicalDevice, &createInfo, nullptr, &device); result != VK_SUCCESS) { + LOG_CRIT(L"Couldn't create vulkanDevice %S", string_VkResult(result)); + } + + return device; +} + +VulkanObj* initVulkan(SDL_Window* window, VkSurfaceKHR& surface, bool enableValidation) { + LOG_USE_MODULE(vulkanSetup); + auto obj = new VulkanObj; + + // Create Instance&Debug + VkDebugUtilsMessengerCreateInfoEXT dbgCreateInfo { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .messageSeverity = + static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) | + static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) | static_cast(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT), + .messageType = static_cast(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) | + static_cast(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) | + static_cast(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT), + .pfnUserCallback = debugMessengerCallback, + .pUserData = nullptr, + }; + + VkApplicationInfo const appInfo { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = nullptr, + .pApplicationName = "psOff", + .applicationVersion = 1, + .pEngineName = "psOff_render", + .engineVersion = 1, + .apiVersion = VK_API_VERSION_1_3, + }; + + auto extensions = getExtensions(window, enableValidation); + + std::vector reqLayers; + reqLayers.reserve(1 + extensions.requiredLayers.size()); + + for (auto& req: extensions.requiredLayers) { + reqLayers.push_back(req.c_str()); + } + + std::vector reqExt; + reqExt.reserve(1 + extensions.requiredLayers.size()); + + for (auto& req: extensions.requiredExtensions) { + reqExt.push_back(req.c_str()); + } + + VkInstanceCreateInfo const instInfo { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = extensions.enableValidationLayers ? &dbgCreateInfo : nullptr, + .flags = 0, + .pApplicationInfo = &appInfo, + .enabledLayerCount = (uint32_t)reqLayers.size(), + .ppEnabledLayerNames = reqLayers.data(), + .enabledExtensionCount = (uint32_t)reqExt.size(), + .ppEnabledExtensionNames = reqExt.data(), + }; + + if (VkResult result = vkCreateInstance(&instInfo, nullptr, &obj->deviceInfo->instance); result != VK_SUCCESS) { + if (result == VK_ERROR_INCOMPATIBLE_DRIVER) + LOG_CRIT(L"vkCreateInstance() Error:VK_ERROR_INCOMPATIBLE_DRIVER"); + else + LOG_CRIT(L"vkCreateInstance() Error:%S", string_VkResult(result)); + } + + if (extensions.enableValidationLayers) { + // dbgCreateInfo.pNext = nullptr; + if (auto result = createDebugUtilsMessengerEXT(obj->deviceInfo->instance, &dbgCreateInfo, nullptr, &obj->debugMessenger); result != VK_SUCCESS) { + LOG_CRIT(L"createDebugUtilsMessengerEXT() %S", string_VkResult(result)); + } + } + // - + + if (auto result = SDL_Vulkan_CreateSurface(window, obj->deviceInfo->instance, &surface); result != true) { + LOG_CRIT(L"SDL_Vulkan_CreateSurface() Error:%S", SDL_GetError()); + } + + VulkanQueues queues; + findPhysicalDevice(obj->deviceInfo->instance, surface, &obj->surfaceCapabilities, &obj->deviceInfo->physicalDevice, &queues, enableValidation); + if (obj->deviceInfo->physicalDevice == nullptr) { + LOG_CRIT(L"Couldn't find a suitable device"); + } + + vkGetPhysicalDeviceProperties(obj->deviceInfo->physicalDevice, &obj->deviceInfo->devProperties); + + { + auto const text = + std::format("Selected GPU:{} api:{}.{}.{}", obj->deviceInfo->devProperties.deviceName, VK_API_VERSION_MAJOR(obj->deviceInfo->devProperties.apiVersion), + VK_API_VERSION_MINOR(obj->deviceInfo->devProperties.apiVersion), VK_API_VERSION_PATCH(obj->deviceInfo->devProperties.apiVersion)); + printf("%s\n", text.data()); + LOG_INFO(L"%S", text.data()); + + // Debug infos + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(obj->deviceInfo->physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + LOG_DEBUG(L"%u| Memory Type: index:%u flags:%S", i, memProperties.memoryTypes[i].heapIndex, + string_VkMemoryPropertyFlags(memProperties.memoryTypes[i].propertyFlags).data()); + } + for (uint32_t i = 0; i < memProperties.memoryHeapCount; i++) { + LOG_DEBUG(L"%u| Memory Heap: size:%u flags:%S", i, memProperties.memoryHeaps[i].size, + string_VkMemoryHeapFlags(memProperties.memoryHeaps[i].flags).data()); + } + // - + } + + obj->deviceInfo->device = createDevice(obj->deviceInfo->physicalDevice, surface, extensions, queues, enableValidation); + + // Create queues + { + auto queueFunc = [](VkDevice device, std::vector const queueInfos, std::vector>& out) { + for (auto& item: queueInfos) { + VkQueue queue; + vkGetDeviceQueue(device, item.family, item.index, &queue); + out.push_back(std::make_unique(queue, item.family)); + } + }; + + queueFunc(obj->deviceInfo->device, queues.compute, obj->queues.items[getIndex(QueueType::compute)]); + queueFunc(obj->deviceInfo->device, queues.present, obj->queues.items[getIndex(QueueType::present)]); + queueFunc(obj->deviceInfo->device, queues.transfer, obj->queues.items[getIndex(QueueType::transfer)]); + queueFunc(obj->deviceInfo->device, queues.graphics, obj->queues.items[getIndex(QueueType::graphics)]); + } + //- + return obj; +} + +void deinitVulkan(VulkanObj* obj) { + vkDestroyInstance(obj->deviceInfo->instance, nullptr); + vkDestroyDevice(obj->deviceInfo->device, nullptr); + + delete obj; +} +} // namespace vulkan diff --git a/core/videoout/vulkan/vulkanSetup.h b/core/videoout/vulkan/vulkanSetup.h new file mode 100644 index 00000000..bbcfae12 --- /dev/null +++ b/core/videoout/vulkan/vulkanSetup.h @@ -0,0 +1,81 @@ +#pragma once + +#include "vulkanTypes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct SDL_Window; + +namespace vulkan { + +enum class QueueType : uint8_t { + graphics = 0, + compute, + transfer, + present, + numTypes, +}; + +constexpr std::underlying_type::type getIndex(QueueType type) { + return (std::underlying_type::type)type; +} + +struct QueueInfo { + VkQueue queue = nullptr; + uint32_t family = 0; + size_t useCount = 0; + + mutable std::mutex mutex; // sync queue submit access + + QueueInfo(VkQueue queue_, uint32_t family_): queue(queue_), family(family_) {} +}; + +struct Queues { + std::array>, getIndex(QueueType::numTypes)> items {}; /// first: VkQueue, second: familyindex +}; + +struct SwapchainData { + + struct DisplayBuffers { + uint64_t bufferVaddr = 0; + uint32_t bufferSize = 0; + uint32_t bufferAlign = 0; + }; + + std::vector buffers; +}; + +struct SurfaceCapabilities { + VkSurfaceCapabilitiesKHR capabilities {}; + std::vector formats; + std::vector presentModes; + + bool format_srgb_bgra32 = false; + bool format_unorm_bgra32 = false; +}; + +struct VulkanObj { + std::shared_ptr deviceInfo; + + VkDebugUtilsMessengerEXT debugMessenger = nullptr; + SurfaceCapabilities surfaceCapabilities; + Queues queues; + + VulkanObj() { deviceInfo = std::make_shared(); } +}; + +VulkanObj* initVulkan(SDL_Window* window, VkSurfaceKHR& surface, bool useValidation); +void deinitVulkan(VulkanObj* obj); + +void createSurface(VulkanObj* obj, SDL_Window* window, VkSurfaceKHR& surfaceOut); + +std::pair getDisplayFormat(VulkanObj* obj); +} // namespace vulkan diff --git a/core/videoout/vulkan/vulkanTypes.h b/core/videoout/vulkan/vulkanTypes.h new file mode 100644 index 00000000..e670dcbc --- /dev/null +++ b/core/videoout/vulkan/vulkanTypes.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace vulkan { + +struct DeviceInfo { + VkInstance instance = nullptr; + VkPhysicalDevice physicalDevice = nullptr; + VkDevice device = nullptr; + + VkPhysicalDeviceProperties devProperties; +}; +} // namespace vulkan \ No newline at end of file