feat(libs): add rpx library build as libs target

This commit is contained in:
boykopovar 2026-08-20 17:06:41 +03:00
parent dd2481951d
commit 9e21a054e0
8 changed files with 529 additions and 0 deletions

View File

@ -30,3 +30,5 @@ target_include_directories(relinker PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/codegen/include
${CMAKE_CURRENT_SOURCE_DIR}/relinker/include
)
add_subdirectory(libs)

36
core/libs/CMakeLists.txt Normal file
View File

@ -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 $<TARGET_FILE:nid_patcher> ${LIB_NAME} $<TARGET_FILE:${LIB_NAME}>
COMMENT "Patching NIDs"
)
list(APPEND LIB_TARGETS ${LIB_NAME})
endif()
endforeach()
add_custom_target(libs DEPENDS nid_patcher ${LIB_TARGETS})

View File

@ -0,0 +1,29 @@
#include <cstdint>
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;
}
}

View File

@ -0,0 +1,12 @@
#ifndef NID_NIDCOMPUTE_HPP
#define NID_NIDCOMPUTE_HPP
#include <string>
namespace Nid {
std::string ComputeNid(const std::string& symbolName, const std::string& libraryName);
}
#endif

View File

@ -0,0 +1,14 @@
#ifndef NID_SHA1_HPP
#define NID_SHA1_HPP
#include <cstdint>
#include <array>
#include <vector>
namespace Nid {
std::array<std::uint8_t, 20> Sha1(const std::vector<std::uint8_t>& data);
}
#endif

View File

@ -0,0 +1,50 @@
#include <nid/NidCompute.hpp>
#include <nid/Sha1.hpp>
#include <stdexcept>
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<std::uint32_t>(data[i]) << 16u;
if (i + 1u < len) group |= static_cast<std::uint32_t>(data[i+1u]) << 8u;
if (i + 2u < len) group |= static_cast<std::uint32_t>(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<std::uint8_t> input;
input.reserve(symbolName.size() + 1u + libraryName.size() + 1u + libraryName.size() + 1u);
for (char c : symbolName) input.push_back(static_cast<std::uint8_t>(c));
input.push_back(0x00u);
for (char c : libraryName) input.push_back(static_cast<std::uint8_t>(c));
input.push_back(0x00u);
for (char c : libraryName) input.push_back(static_cast<std::uint8_t>(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) + '#';
}
}

View File

@ -0,0 +1,80 @@
#include <nid/Sha1.hpp>
#include <cstdint>
#include <array>
#include <vector>
namespace Nid {
namespace {
constexpr std::uint32_t _rotl32(std::uint32_t x, unsigned n) {
return (x << n) | (x >> (32u - n));
}
}
std::array<std::uint8_t, 20> Sha1(const std::vector<std::uint8_t>& 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<std::uint8_t> msg(data);
const std::uint64_t bitLen = static_cast<std::uint64_t>(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<std::uint8_t>((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<std::uint32_t>(msg[offset + i * 4u + 0u]) << 24u)
| (static_cast<std::uint32_t>(msg[offset + i * 4u + 1u]) << 16u)
| (static_cast<std::uint32_t>(msg[offset + i * 4u + 2u]) << 8u)
| (static_cast<std::uint32_t>(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<std::uint8_t, 20> digest;
auto _put = [&](int base, std::uint32_t v) {
digest[base+0] = static_cast<std::uint8_t>(v >> 24u);
digest[base+1] = static_cast<std::uint8_t>(v >> 16u);
digest[base+2] = static_cast<std::uint8_t>(v >> 8u);
digest[base+3] = static_cast<std::uint8_t>(v);
};
_put(0, h0); _put(4, h1); _put(8, h2); _put(12, h3); _put(16, h4);
return digest;
}
}

306
core/libs/nid/src/main.cpp Normal file
View File

@ -0,0 +1,306 @@
#include <nid/NidCompute.hpp>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <limits>
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<typename T>
T _read(const std::vector<std::uint8_t>& 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<typename T>
void _write(std::vector<std::uint8_t>& 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<std::uint8_t>& 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<char>(buf[offset++]));
return s;
}
std::vector<std::uint8_t> _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<char>(f), {}};
}
void _writeFile(const std::string& path, const std::vector<std::uint8_t>& data) {
std::ofstream f(path, std::ios::binary);
if (!f) throw std::runtime_error("cannot write: " + path);
f.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
}
enum class BinaryFormat { Elf64, Pe };
BinaryFormat _detectFormat(const std::vector<std::uint8_t>& 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<std::uint8_t>& 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<Elf64_Ehdr>(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<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = _read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type == SHT_DYNSYM) {
dynSymOffset = static_cast<std::size_t>(shdr.sh_offset);
dynSymSize = static_cast<std::size_t>(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<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = _read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type == SHT_DYNSYM) return static_cast<std::size_t>(shdr.sh_link);
}
throw std::runtime_error("cannot find dynsym sh_link");
}();
{
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + dynStrSectionLink * sizeof(Elf64_Shdr);
const auto shdr = _read<Elf64_Shdr>(elf, shOffset);
dynStrOffset = static_cast<std::size_t>(shdr.sh_offset);
dynStrSize = static_cast<std::size_t>(shdr.sh_size);
}
if (dynStrOffset == 0u) throw std::runtime_error("no .dynstr section");
std::vector<std::uint8_t> newDynStr(elf.begin() + static_cast<std::ptrdiff_t>(dynStrOffset),
elf.begin() + static_cast<std::ptrdiff_t>(dynStrOffset + dynStrSize));
std::vector<std::pair<std::uint32_t, std::uint32_t>> nameRemap;
for (std::size_t i = 1u; i < symCount; ++i) {
const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym);
auto sym = _read<Elf64_Sym>(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<std::uint32_t>::max();
}();
if (existingIt != std::numeric_limits<std::uint32_t>::max()) {
sym.st_name = existingIt;
_write(elf, symOffset, sym);
continue;
}
const auto newNameOffset = static_cast<std::uint32_t>(newDynStr.size());
for (char c : nid) newDynStr.push_back(static_cast<std::uint8_t>(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<std::uint8_t>& pe, std::uint32_t rva, std::uint32_t peHeaderOffset, std::uint16_t numberOfSections, std::uint32_t sizeOfOptionalHeader) {
const std::size_t sectionTableOffset = static_cast<std::size_t>(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<std::uint32_t>(pe, sectionOffset + 8u);
const auto virtualAddress = _read<std::uint32_t>(pe, sectionOffset + 12u);
const auto sizeOfRawData = _read<std::uint32_t>(pe, sectionOffset + 16u);
const auto pointerToRawData = _read<std::uint32_t>(pe, sectionOffset + 20u);
const std::uint32_t effectiveSize = virtualSize != 0u ? virtualSize : sizeOfRawData;
if (rva >= virtualAddress && rva < virtualAddress + effectiveSize)
return static_cast<std::size_t>(pointerToRawData) + (rva - virtualAddress);
}
throw std::runtime_error("rva not mapped to any section");
}
void _patchNidsPe(std::vector<std::uint8_t>& pe, const std::string& libraryName) {
if (pe.size() < 0x40) throw std::runtime_error("file too small");
const auto peHeaderOffset = _read<std::uint32_t>(pe, 0x3cu);
if (static_cast<std::size_t>(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<std::size_t>(peHeaderOffset) + 4u;
const auto numberOfSections = _read<std::uint16_t>(pe, coffHeaderOffset + 2u);
const auto sizeOfOptionalHeader = _read<std::uint16_t>(pe, coffHeaderOffset + 16u);
if (sizeOfOptionalHeader == 0u) throw std::runtime_error("no optional header");
const std::size_t optionalHeaderOffset = coffHeaderOffset + 20u;
const auto magic = _read<std::uint16_t>(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<PeDataDirectory>(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<PeExportDirectory>(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<std::pair<std::size_t, std::string>> nameOffsets;
for (std::uint32_t i = 0u; i < exportTable.NumberOfNames; ++i) {
const auto nameRva = _read<std::uint32_t>(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<std::uint8_t>& 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 <library_name> <file.so> [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;
}