From 9e21a054e0202b66d5f6358c66bd544a5ce7c0a6 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Thu, 20 Aug 2026 17:06:41 +0300 Subject: [PATCH] feat(libs): add rpx library build as libs target --- core/CMakeLists.txt | 2 + core/libs/CMakeLists.txt | 36 +++ core/libs/libSceAudio/Export.cpp | 29 +++ core/libs/nid/include/nid/NidCompute.hpp | 12 + core/libs/nid/include/nid/Sha1.hpp | 14 ++ core/libs/nid/src/NidCompute.cpp | 50 ++++ core/libs/nid/src/Sha1.cpp | 80 ++++++ core/libs/nid/src/main.cpp | 306 +++++++++++++++++++++++ 8 files changed, 529 insertions(+) create mode 100644 core/libs/CMakeLists.txt create mode 100644 core/libs/libSceAudio/Export.cpp create mode 100644 core/libs/nid/include/nid/NidCompute.hpp create mode 100644 core/libs/nid/include/nid/Sha1.hpp create mode 100644 core/libs/nid/src/NidCompute.cpp create mode 100644 core/libs/nid/src/Sha1.cpp create mode 100644 core/libs/nid/src/main.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index efe370fc6..8d46ec84d 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -30,3 +30,5 @@ target_include_directories(relinker PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/codegen/include ${CMAKE_CURRENT_SOURCE_DIR}/relinker/include ) + +add_subdirectory(libs) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt new file mode 100644 index 000000000..8cf68079b --- /dev/null +++ b/core/libs/CMakeLists.txt @@ -0,0 +1,36 @@ +add_executable(nid_patcher EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/Sha1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/NidCompute.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/main.cpp +) +target_include_directories(nid_patcher PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) +target_compile_features(nid_patcher PRIVATE cxx_std_20) + +file(GLOB LIBS_DIRS LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/lib*) + +set(LIB_TARGETS) + +foreach(LIB_DIR ${LIBS_DIRS}) + if(IS_DIRECTORY ${LIB_DIR}) + get_filename_component(LIB_NAME ${LIB_DIR} NAME) + + add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL ${LIB_DIR}/Export.cpp) + set_target_properties(${LIB_NAME} PROPERTIES + PREFIX "" + SUFFIX .rpx + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libs + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/libs + ) + + add_dependencies(${LIB_NAME} nid_patcher) + + add_custom_command(TARGET ${LIB_NAME} POST_BUILD + COMMAND $ ${LIB_NAME} $ + COMMENT "Patching NIDs" + ) + + list(APPEND LIB_TARGETS ${LIB_NAME}) + endif() +endforeach() + +add_custom_target(libs DEPENDS nid_patcher ${LIB_TARGETS}) diff --git a/core/libs/libSceAudio/Export.cpp b/core/libs/libSceAudio/Export.cpp new file mode 100644 index 000000000..ebcc805b6 --- /dev/null +++ b/core/libs/libSceAudio/Export.cpp @@ -0,0 +1,29 @@ +#include + +extern "C" { + + using SceAudioOutPortId = std::int32_t; + + std::int32_t sceAudioOutInit(void) { return 0; } + + SceAudioOutPortId sceAudioOutOpen(std::int32_t userId, std::int32_t type, std::int32_t index, std::uint32_t len, std::uint32_t freq, std::uint32_t param) { + (void)userId; (void)type; (void)index; (void)len; (void)freq; (void)param; + return 0; + } + + std::int32_t sceAudioOutOutput(SceAudioOutPortId handle, const void* ptr) { + (void)handle; (void)ptr; + return 0; + } + + std::int32_t sceAudioOutSetVolume(SceAudioOutPortId handle, std::int32_t flag, std::int32_t* vol) { + (void)handle; (void)flag; (void)vol; + return 0; + } + + std::int32_t sceAudioOutClose(SceAudioOutPortId handle) { + (void)handle; + return 0; + } + +} diff --git a/core/libs/nid/include/nid/NidCompute.hpp b/core/libs/nid/include/nid/NidCompute.hpp new file mode 100644 index 000000000..e0d0ff7c5 --- /dev/null +++ b/core/libs/nid/include/nid/NidCompute.hpp @@ -0,0 +1,12 @@ +#ifndef NID_NIDCOMPUTE_HPP +#define NID_NIDCOMPUTE_HPP + +#include + +namespace Nid { + +std::string ComputeNid(const std::string& symbolName, const std::string& libraryName); + +} + +#endif diff --git a/core/libs/nid/include/nid/Sha1.hpp b/core/libs/nid/include/nid/Sha1.hpp new file mode 100644 index 000000000..9010b21e1 --- /dev/null +++ b/core/libs/nid/include/nid/Sha1.hpp @@ -0,0 +1,14 @@ +#ifndef NID_SHA1_HPP +#define NID_SHA1_HPP + +#include +#include +#include + +namespace Nid { + +std::array Sha1(const std::vector& data); + +} + +#endif diff --git a/core/libs/nid/src/NidCompute.cpp b/core/libs/nid/src/NidCompute.cpp new file mode 100644 index 000000000..580a0ca5d --- /dev/null +++ b/core/libs/nid/src/NidCompute.cpp @@ -0,0 +1,50 @@ +#include +#include +#include + +namespace Nid { + +namespace { + +constexpr char kBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; + +std::string _base64Encode(const std::uint8_t* data, std::size_t len) { + std::string out; + out.reserve(((len + 2u) / 3u) * 4u); + for (std::size_t i = 0; i < len; i += 3u) { + std::uint32_t group = static_cast(data[i]) << 16u; + if (i + 1u < len) group |= static_cast(data[i+1u]) << 8u; + if (i + 2u < len) group |= static_cast(data[i+2u]); + + out.push_back(kBase64Chars[(group >> 18u) & 0x3fu]); + out.push_back(kBase64Chars[(group >> 12u) & 0x3fu]); + out.push_back((i + 1u < len) ? kBase64Chars[(group >> 6u) & 0x3fu] : '='); + out.push_back((i + 2u < len) ? kBase64Chars[(group) & 0x3fu] : '='); + } + return out; +} + +} + +std::string ComputeNid(const std::string& symbolName, const std::string& libraryName) { + if (symbolName.empty()) throw std::invalid_argument("symbolName is empty"); + if (libraryName.empty()) throw std::invalid_argument("libraryName is empty"); + + std::vector input; + input.reserve(symbolName.size() + 1u + libraryName.size() + 1u + libraryName.size() + 1u); + + for (char c : symbolName) input.push_back(static_cast(c)); + input.push_back(0x00u); + for (char c : libraryName) input.push_back(static_cast(c)); + input.push_back(0x00u); + for (char c : libraryName) input.push_back(static_cast(c)); + input.push_back(0x00u); + + const auto digest = Sha1(input); + const std::string b64 = _base64Encode(digest.data(), digest.size()); + + if (b64.size() < 11u) throw std::runtime_error("base64 output too short"); + return b64.substr(0u, 11u) + '#'; +} + +} diff --git a/core/libs/nid/src/Sha1.cpp b/core/libs/nid/src/Sha1.cpp new file mode 100644 index 000000000..0ff105654 --- /dev/null +++ b/core/libs/nid/src/Sha1.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +namespace Nid { + +namespace { + +constexpr std::uint32_t _rotl32(std::uint32_t x, unsigned n) { + return (x << n) | (x >> (32u - n)); +} + +} + +std::array Sha1(const std::vector& data) { + std::uint32_t h0 = 0x67452301u; + std::uint32_t h1 = 0xEFCDAB89u; + std::uint32_t h2 = 0x98BADCFEu; + std::uint32_t h3 = 0x10325476u; + std::uint32_t h4 = 0xC3D2E1F0u; + + std::vector msg(data); + + const std::uint64_t bitLen = static_cast(data.size()) * 8u; + + msg.push_back(0x80u); + while ((msg.size() % 64u) != 56u) + msg.push_back(0x00u); + + for (int i = 7; i >= 0; --i) + msg.push_back(static_cast((bitLen >> (i * 8u)) & 0xffu)); + + for (std::size_t offset = 0; offset < msg.size(); offset += 64u) { + std::uint32_t w[80]; + for (int i = 0; i < 16; ++i) { + w[i] = (static_cast(msg[offset + i * 4u + 0u]) << 24u) + | (static_cast(msg[offset + i * 4u + 1u]) << 16u) + | (static_cast(msg[offset + i * 4u + 2u]) << 8u) + | (static_cast(msg[offset + i * 4u + 3u])); + } + for (int i = 16; i < 80; ++i) + w[i] = _rotl32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1u); + + std::uint32_t a = h0, b = h1, c = h2, d = h3, e = h4; + + for (int i = 0; i < 80; ++i) { + std::uint32_t f, k; + if (i < 20) { + f = (b & c) | ((~b) & d); + k = 0x5A827999u; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1u; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDCu; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6u; + } + std::uint32_t temp = _rotl32(a, 5u) + f + e + k + w[i]; + e = d; d = c; c = _rotl32(b, 30u); b = a; a = temp; + } + + h0 += a; h1 += b; h2 += c; h3 += d; h4 += e; + } + + std::array digest; + auto _put = [&](int base, std::uint32_t v) { + digest[base+0] = static_cast(v >> 24u); + digest[base+1] = static_cast(v >> 16u); + digest[base+2] = static_cast(v >> 8u); + digest[base+3] = static_cast(v); + }; + _put(0, h0); _put(4, h1); _put(8, h2); _put(12, h3); _put(16, h4); + return digest; +} + +} diff --git a/core/libs/nid/src/main.cpp b/core/libs/nid/src/main.cpp new file mode 100644 index 000000000..2f25371da --- /dev/null +++ b/core/libs/nid/src/main.cpp @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct Elf64_Ehdr { + std::uint8_t e_ident[16]; + std::uint16_t e_type; + std::uint16_t e_machine; + std::uint32_t e_version; + std::uint64_t e_entry; + std::uint64_t e_phoff; + std::uint64_t e_shoff; + std::uint32_t e_flags; + std::uint16_t e_ehsize; + std::uint16_t e_phentsize; + std::uint16_t e_phnum; + std::uint16_t e_shentsize; + std::uint16_t e_shnum; + std::uint16_t e_shstrndx; +}; + +struct Elf64_Shdr { + std::uint32_t sh_name; + std::uint32_t sh_type; + std::uint64_t sh_flags; + std::uint64_t sh_addr; + std::uint64_t sh_offset; + std::uint64_t sh_size; + std::uint32_t sh_link; + std::uint32_t sh_info; + std::uint64_t sh_addralign; + std::uint64_t sh_entsize; +}; + +struct Elf64_Sym { + std::uint32_t st_name; + std::uint8_t st_info; + std::uint8_t st_other; + std::uint16_t st_shndx; + std::uint64_t st_value; + std::uint64_t st_size; +}; + +constexpr std::uint32_t SHT_DYNSYM = 11u; +constexpr std::uint8_t STB_LOCAL = 0u; + +struct PeDataDirectory { + std::uint32_t VirtualAddress; + std::uint32_t Size; +}; + +struct PeExportDirectory { + std::uint32_t Characteristics; + std::uint32_t TimeDateStamp; + std::uint16_t MajorVersion; + std::uint16_t MinorVersion; + std::uint32_t Name; + std::uint32_t Base; + std::uint32_t NumberOfFunctions; + std::uint32_t NumberOfNames; + std::uint32_t AddressOfFunctions; + std::uint32_t AddressOfNames; + std::uint32_t AddressOfNameOrdinals; +}; + +template +T _read(const std::vector& buf, std::size_t offset) { + if (offset + sizeof(T) > buf.size()) throw std::runtime_error("read out of bounds"); + T v; + std::memcpy(&v, buf.data() + offset, sizeof(T)); + return v; +} + +template +void _write(std::vector& buf, std::size_t offset, const T& v) { + if (offset + sizeof(T) > buf.size()) throw std::runtime_error("write out of bounds"); + std::memcpy(buf.data() + offset, &v, sizeof(T)); +} + +std::string _readCStr(const std::vector& buf, std::size_t offset) { + if (offset >= buf.size()) throw std::runtime_error("cstr offset out of bounds"); + std::string s; + while (offset < buf.size() && buf[offset] != 0u) + s.push_back(static_cast(buf[offset++])); + return s; +} + +std::vector _readFile(const std::string& path) { + std::ifstream f(path, std::ios::binary); + if (!f) throw std::runtime_error("cannot open: " + path); + return {std::istreambuf_iterator(f), {}}; +} + +void _writeFile(const std::string& path, const std::vector& data) { + std::ofstream f(path, std::ios::binary); + if (!f) throw std::runtime_error("cannot write: " + path); + f.write(reinterpret_cast(data.data()), static_cast(data.size())); +} + +enum class BinaryFormat { Elf64, Pe }; + +BinaryFormat _detectFormat(const std::vector& buf) { + if (buf.size() >= 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F') return BinaryFormat::Elf64; + if (buf.size() >= 2 && buf[0] == 'M' && buf[1] == 'Z') return BinaryFormat::Pe; + throw std::runtime_error("unrecognized binary format"); +} + +void _patchNidsElf(std::vector& elf, const std::string& libraryName) { + if (elf.size() < sizeof(Elf64_Ehdr)) throw std::runtime_error("file too small"); + if (elf[4] != 2) throw std::runtime_error("not ELF64"); + + const auto ehdr = _read(elf, 0u); + + std::size_t dynSymOffset = 0u, dynSymSize = 0u; + std::size_t dynStrOffset = 0u, dynStrSize = 0u; + + for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { + const std::size_t shOffset = static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr); + const auto shdr = _read(elf, shOffset); + if (shdr.sh_type == SHT_DYNSYM) { + dynSymOffset = static_cast(shdr.sh_offset); + dynSymSize = static_cast(shdr.sh_size); + } + } + + if (dynSymOffset == 0u) throw std::runtime_error("no .dynsym section"); + + const std::size_t symCount = dynSymSize / sizeof(Elf64_Sym); + if (symCount == 0u) throw std::runtime_error(".dynsym is empty"); + + const std::size_t dynStrSectionLink = [&]() -> std::size_t { + for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) { + const std::size_t shOffset = static_cast(ehdr.e_shoff) + i * sizeof(Elf64_Shdr); + const auto shdr = _read(elf, shOffset); + if (shdr.sh_type == SHT_DYNSYM) return static_cast(shdr.sh_link); + } + throw std::runtime_error("cannot find dynsym sh_link"); + }(); + + { + const std::size_t shOffset = static_cast(ehdr.e_shoff) + dynStrSectionLink * sizeof(Elf64_Shdr); + const auto shdr = _read(elf, shOffset); + dynStrOffset = static_cast(shdr.sh_offset); + dynStrSize = static_cast(shdr.sh_size); + } + + if (dynStrOffset == 0u) throw std::runtime_error("no .dynstr section"); + + std::vector newDynStr(elf.begin() + static_cast(dynStrOffset), + elf.begin() + static_cast(dynStrOffset + dynStrSize)); + + std::vector> nameRemap; + + for (std::size_t i = 1u; i < symCount; ++i) { + const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym); + auto sym = _read(elf, symOffset); + + const std::uint8_t binding = sym.st_info >> 4u; + if (binding == STB_LOCAL) continue; + if (sym.st_name == 0u) continue; + + const std::string symName = _readCStr(newDynStr, sym.st_name); + if (symName.empty()) continue; + + const std::string nid = Nid::ComputeNid(symName, libraryName); + + const auto existingIt = [&]() { + for (const auto& [orig, newIdx] : nameRemap) + if (orig == sym.st_name) return newIdx; + return std::numeric_limits::max(); + }(); + + if (existingIt != std::numeric_limits::max()) { + sym.st_name = existingIt; + _write(elf, symOffset, sym); + continue; + } + + const auto newNameOffset = static_cast(newDynStr.size()); + for (char c : nid) newDynStr.push_back(static_cast(c)); + newDynStr.push_back(0u); + + nameRemap.emplace_back(sym.st_name, newNameOffset); + sym.st_name = newNameOffset; + _write(elf, symOffset, sym); + } + + if (newDynStr.size() > dynStrSize) throw std::runtime_error("new .dynstr exceeds original size - rebuild with padding"); + + std::memcpy(elf.data() + dynStrOffset, newDynStr.data(), newDynStr.size()); + for (std::size_t i = newDynStr.size(); i < dynStrSize; ++i) + elf[dynStrOffset + i] = 0u; +} + +std::size_t _peRvaToOffset(const std::vector& pe, std::uint32_t rva, std::uint32_t peHeaderOffset, std::uint16_t numberOfSections, std::uint32_t sizeOfOptionalHeader) { + const std::size_t sectionTableOffset = static_cast(peHeaderOffset) + 4u + 20u + sizeOfOptionalHeader; + for (std::uint16_t i = 0u; i < numberOfSections; ++i) { + const std::size_t sectionOffset = sectionTableOffset + i * 40u; + if (sectionOffset + 40u > pe.size()) throw std::runtime_error("section header out of bounds"); + const auto virtualSize = _read(pe, sectionOffset + 8u); + const auto virtualAddress = _read(pe, sectionOffset + 12u); + const auto sizeOfRawData = _read(pe, sectionOffset + 16u); + const auto pointerToRawData = _read(pe, sectionOffset + 20u); + const std::uint32_t effectiveSize = virtualSize != 0u ? virtualSize : sizeOfRawData; + if (rva >= virtualAddress && rva < virtualAddress + effectiveSize) + return static_cast(pointerToRawData) + (rva - virtualAddress); + } + throw std::runtime_error("rva not mapped to any section"); +} + +void _patchNidsPe(std::vector& pe, const std::string& libraryName) { + if (pe.size() < 0x40) throw std::runtime_error("file too small"); + + const auto peHeaderOffset = _read(pe, 0x3cu); + if (static_cast(peHeaderOffset) + 4u > pe.size()) throw std::runtime_error("invalid pe header offset"); + if (pe[peHeaderOffset] != 'P' || pe[peHeaderOffset + 1u] != 'E' || pe[peHeaderOffset + 2u] != 0u || pe[peHeaderOffset + 3u] != 0u) + throw std::runtime_error("not a PE file"); + + const std::size_t coffHeaderOffset = static_cast(peHeaderOffset) + 4u; + const auto numberOfSections = _read(pe, coffHeaderOffset + 2u); + const auto sizeOfOptionalHeader = _read(pe, coffHeaderOffset + 16u); + if (sizeOfOptionalHeader == 0u) throw std::runtime_error("no optional header"); + + const std::size_t optionalHeaderOffset = coffHeaderOffset + 20u; + const auto magic = _read(pe, optionalHeaderOffset); + + std::size_t dataDirectoryOffset; + if (magic == 0x20bu) { + dataDirectoryOffset = optionalHeaderOffset + 112u; + } else if (magic == 0x10bu) { + dataDirectoryOffset = optionalHeaderOffset + 96u; + } else { + throw std::runtime_error("unsupported optional header magic"); + } + + const auto exportDir = _read(pe, dataDirectoryOffset); + if (exportDir.VirtualAddress == 0u) throw std::runtime_error("no export directory"); + + const std::size_t exportDirOffset = _peRvaToOffset(pe, exportDir.VirtualAddress, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const auto exportTable = _read(pe, exportDirOffset); + + if (exportTable.NumberOfNames == 0u) throw std::runtime_error("no exported names"); + + const std::size_t namesArrayOffset = _peRvaToOffset(pe, exportTable.AddressOfNames, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + + std::vector> nameOffsets; + for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) { + const auto nameRva = _read(pe, namesArrayOffset + i * 4u); + const std::size_t nameOffset = _peRvaToOffset(pe, nameRva, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + const std::string name = _readCStr(pe, nameOffset); + if (name.empty()) throw std::runtime_error("empty exported name"); + nameOffsets.emplace_back(nameOffset, name); + } + + for (const auto& [nameOffset, name] : nameOffsets) { + const std::string nid = Nid::ComputeNid(name, libraryName); + if (nid.size() > name.size()) throw std::runtime_error("nid longer than original export name - cannot patch in place"); + + std::memcpy(pe.data() + nameOffset, nid.data(), nid.size()); + pe[nameOffset + nid.size()] = 0u; + for (std::size_t i = nid.size() + 1u; i < name.size() + 1u; ++i) + pe[nameOffset + i] = 0u; + } +} + +void _patchNids(std::vector& binary, const std::string& libraryName) { + switch (_detectFormat(binary)) { + case BinaryFormat::Elf64: _patchNidsElf(binary, libraryName); return; + case BinaryFormat::Pe: _patchNidsPe(binary, libraryName); return; + } + throw std::runtime_error("unreachable"); +} + +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + std::cerr << "Usage: nid_patcher [file2.so ...]\n"; + return 1; + } + + const std::string libraryName = argv[1]; + + for (int i = 2; i < argc; ++i) { + const std::string path = argv[i]; + try { + auto binary = _readFile(path); + _patchNids(binary, libraryName); + _writeFile(path, binary); + std::cout << "OK: " << path << "\n"; + } catch (const std::exception& e) { + std::cerr << "FAIL: " << path << ": " << e.what() << "\n"; + return 2; + } + } + + return 0; +}