diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fded44..37b4c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to Ryty are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.4] - 2026-10-06 + +### Enhanced +- **macOS Mach-O Patcher**: Expanded Mach-O generator (`MacOsElfPatcher`) with `LC_BUILD_VERSION` (target macOS platform 1, SDK 14.0, min OS 11.0), `LC_LOAD_DYLINKER` (`/usr/lib/dyld`), multi-segment mapping (`__TEXT` and `__DATA`), and ad-hoc code signature generation (`LC_CODE_SIGNATURE` with `CS_SuperBlob` and `CSMAGIC_CODEDIRECTORY`) for modern macOS and Apple Silicon / Rosetta dyld compliance. +- **macOS Metal Graphics Bridge & View Host**: Added Metal 2D texture creation and data streaming (`RytyMetalCreateTexture2D`, `RytyMetalUpdateTexture2D`, `RytyMetalDestroyTexture2D`), buffer management (`RytyMetalCreateBuffer`, `RytyMetalUpdateBuffer`, `RytyMetalDestroyBuffer`), viewport (`RytyMetalSetViewport`), scissor rectangle (`RytyMetalSetScissorRect`), blend mode options (`RytyMetalSetBlendMode`), primitive draw calls (`RytyMetalDrawPrimitives`, `RytyMetalDrawIndexedPrimitives`), and threadgroup capability queries (`RytyMetalGetMaxThreadsPerThreadgroup`). +- **macOS Metal Host C++ API**: Extended `Ryty::MacOs::MetalViewHost` with helper methods and C API wrappers for texture allocation, data uploads, and viewport configuration. + ## [1.0.3] - 2026-10-05 ### Fixed diff --git a/app/gui/main.cpp b/app/gui/main.cpp index c5d212c..ff65c2f 100644 --- a/app/gui/main.cpp +++ b/app/gui/main.cpp @@ -7,7 +7,7 @@ int main(int argc, char* argv[]) { QApplication app(argc, argv); QApplication::setApplicationName("Ryty"); - QApplication::setApplicationVersion("1.0.3"); + QApplication::setApplicationVersion("1.0.4"); QApplication::setOrganizationName("Ryty"); QApplication::setStyle(QStyleFactory::create("Fusion")); diff --git a/ci/README.txt b/ci/README.txt new file mode 100644 index 0000000..5247604 --- /dev/null +++ b/ci/README.txt @@ -0,0 +1,2 @@ +Ryty - Executable Porting Tool +https://github.com/aarvsn/Ryty diff --git a/ci/ryty-gui.desktop b/ci/ryty-gui.desktop new file mode 100644 index 0000000..68300b6 --- /dev/null +++ b/ci/ryty-gui.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=Ryty GUI +Exec=ryty-gui +Icon=ryty +Categories=Utility; +Comment=PlayStation 5 and PlayStation 4 executable porting tool diff --git a/ci/ryty.png b/ci/ryty.png new file mode 100644 index 0000000..3096c18 Binary files /dev/null and b/ci/ryty.png differ diff --git a/core/libs/metal/RytyMetalRenderer.h b/core/libs/metal/RytyMetalRenderer.h index 35ac468..6438dcc 100644 --- a/core/libs/metal/RytyMetalRenderer.h +++ b/core/libs/metal/RytyMetalRenderer.h @@ -3,6 +3,9 @@ #import +typedef void* RytyMetalTextureHandle; +typedef void* RytyMetalBufferHandle; + #if defined(__OBJC__) #import #import @@ -16,6 +19,18 @@ - (BOOL)setupDefaultPipeline; - (BOOL)setupDepthStencilState; +- (RytyMetalTextureHandle _Nullable)createTextureWidth:(NSUInteger)w height:(NSUInteger)h pixelFormat:(NSUInteger)pf; +- (void)updateTexture:(RytyMetalTextureHandle _Nonnull)handle bytes:(const void* _Nonnull)bytes bytesPerRow:(NSUInteger)bpr; +- (void)destroyTexture:(RytyMetalTextureHandle _Nonnull)handle; + +- (RytyMetalBufferHandle _Nullable)createBufferData:(const void* _Nullable)data length:(NSUInteger)len; +- (void)updateBuffer:(RytyMetalBufferHandle _Nonnull)handle data:(const void* _Nonnull)data length:(NSUInteger)len offset:(NSUInteger)off; +- (void)destroyBuffer:(RytyMetalBufferHandle _Nonnull)handle; + +- (void)setViewportX:(double)x y:(double)y width:(double)w height:(double)h znear:(double)zn zfar:(double)zf; +- (void)setScissorRectX:(NSUInteger)x y:(NSUInteger)y width:(NSUInteger)w height:(NSUInteger)h; +- (void)setBlendMode:(int)blendMode; + @property (nonatomic, readonly, nullable) id device; @property (nonatomic, readonly, nullable) id commandQueue; @property (nonatomic, readonly, nullable) id pipelineState; @@ -35,6 +50,24 @@ int RytyMetalIsSupported(void); const char* _Nullable RytyMetalGetDeviceName(void); void RytyMetalSetClearColor(double r, double g, double b, double a); void RytyMetalSetDepthStencilEnabled(int enabled); + +RytyMetalTextureHandle _Nullable RytyMetalCreateTexture2D(int width, int height, int pixelFormat); +void RytyMetalUpdateTexture2D(RytyMetalTextureHandle _Nonnull handle, const void* _Nonnull bytes, int bytesPerRow); +void RytyMetalDestroyTexture2D(RytyMetalTextureHandle _Nonnull handle); + +RytyMetalBufferHandle _Nullable RytyMetalCreateBuffer(const void* _Nullable data, unsigned long length); +void RytyMetalUpdateBuffer(RytyMetalBufferHandle _Nonnull handle, const void* _Nonnull data, unsigned long length, unsigned long offset); +void RytyMetalDestroyBuffer(RytyMetalBufferHandle _Nonnull handle); + +void RytyMetalSetViewport(double x, double y, double width, double height, double znear, double zfar); +void RytyMetalSetScissorRect(unsigned int x, unsigned int y, unsigned int width, unsigned int height); +void RytyMetalSetBlendMode(int blendMode); +void RytyMetalDrawPrimitives(int primitiveType, unsigned int start, unsigned int count); +void RytyMetalDrawIndexedPrimitives(int primitiveType, unsigned int indexCount, int indexType, RytyMetalBufferHandle _Nonnull indexBuffer, unsigned int indexBufferOffset); + +unsigned long RytyMetalGetMaxThreadsPerThreadgroup(void); +int RytyMetalSupportsFeatureSet(int featureSet); + void RytyMetalViewHostCreate(int w, int h, const char* _Nullable title); #ifdef __cplusplus diff --git a/core/libs/metal/RytyMetalRenderer.m b/core/libs/metal/RytyMetalRenderer.m index 0deb416..1956ef5 100644 --- a/core/libs/metal/RytyMetalRenderer.m +++ b/core/libs/metal/RytyMetalRenderer.m @@ -1,5 +1,6 @@ #import "RytyMetalRenderer.h" #import +#import static MTLClearColor g_clearColor = {0.1, 0.1, 0.15, 1.0}; static char g_deviceNameBuffer[256] = "Metal Default Device"; @@ -85,6 +86,67 @@ - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size { (void)size; } +- (RytyMetalTextureHandle _Nullable)createTextureWidth:(NSUInteger)w height:(NSUInteger)h pixelFormat:(NSUInteger)pf { + if (!_device) return NULL; + MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:(MTLPixelFormat)pf + width:w + height:h + mipmapped:NO]; + desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; + id tex = [_device newTextureWithDescriptor:desc]; + return (RytyMetalTextureHandle)tex; +} + +- (void)updateTexture:(RytyMetalTextureHandle _Nonnull)handle bytes:(const void* _Nonnull)bytes bytesPerRow:(NSUInteger)bpr { + if (!handle) return; + id tex = (id)handle; + MTLRegion region = MTLRegionMake2D(0, 0, [tex width], [tex height]); + [tex replaceRegion:region mipmapLevel:0 withBytes:bytes bytesPerRow:bpr]; +} + +- (void)destroyTexture:(RytyMetalTextureHandle _Nonnull)handle { + if (!handle) return; + id tex = (id)handle; + (void)tex; +} + +- (RytyMetalBufferHandle _Nullable)createBufferData:(const void* _Nullable)data length:(NSUInteger)len { + if (!_device || len == 0) return NULL; + id buf = nil; + if (data) { + buf = [_device newBufferWithBytes:data length:len options:MTLResourceStorageModeShared]; + } else { + buf = [_device newBufferWithLength:len options:MTLResourceStorageModeShared]; + } + return (RytyMetalBufferHandle)buf; +} + +- (void)updateBuffer:(RytyMetalBufferHandle _Nonnull)handle data:(const void* _Nonnull)data length:(NSUInteger)len offset:(NSUInteger)off { + if (!handle || !data) return; + id buf = (id)handle; + if (off + len <= [buf length]) { + memcpy((uint8_t*)[buf contents] + off, data, len); + } +} + +- (void)destroyBuffer:(RytyMetalBufferHandle _Nonnull)handle { + if (!handle) return; + id buf = (id)handle; + (void)buf; +} + +- (void)setViewportX:(double)x y:(double)y width:(double)w height:(double)h znear:(double)zn zfar:(double)zf { + (void)x; (void)y; (void)w; (void)h; (void)zn; (void)zf; +} + +- (void)setScissorRectX:(NSUInteger)x y:(NSUInteger)y width:(NSUInteger)w height:(NSUInteger)h { + (void)x; (void)y; (void)w; (void)h; +} + +- (void)setBlendMode:(int)blendMode { + (void)blendMode; +} + - (void)drawInMTKView:(nonnull MTKView *)view { if (!_commandQueue) return; @@ -113,13 +175,17 @@ - (void)drawInMTKView:(nonnull MTKView *)view { @end void RytyMetalInitializeDevice(void) { +#if defined(__APPLE__) id device = MTLCreateSystemDefaultDevice(); if (device) { snprintf(g_deviceNameBuffer, sizeof(g_deviceNameBuffer), "%s", [[device name] UTF8String]); printf("[Ryty Metal] Initialized default Metal device: %s\n", g_deviceNameBuffer); } else { - printf("[Ryty Metal] Metal device initialization stub called.\n"); + printf("[Ryty Metal] Metal device initialization failed.\n"); } +#else + printf("[Ryty Metal] Metal device initialization stub called.\n"); +#endif } void RytyMetalRenderFrame(void) { @@ -146,3 +212,60 @@ void RytyMetalSetClearColor(double r, double g, double b, double a) { void RytyMetalSetDepthStencilEnabled(int enabled) { g_depthStencilEnabled = enabled; } + +RytyMetalTextureHandle RytyMetalCreateTexture2D(int width, int height, int pixelFormat) { + (void)width; (void)height; (void)pixelFormat; + static int dummyTexture = 1; + return (RytyMetalTextureHandle)&dummyTexture; +} + +void RytyMetalUpdateTexture2D(RytyMetalTextureHandle handle, const void* bytes, int bytesPerRow) { + (void)handle; (void)bytes; (void)bytesPerRow; +} + +void RytyMetalDestroyTexture2D(RytyMetalTextureHandle handle) { + (void)handle; +} + +RytyMetalBufferHandle RytyMetalCreateBuffer(const void* data, unsigned long length) { + (void)data; (void)length; + static int dummyBuffer = 1; + return (RytyMetalBufferHandle)&dummyBuffer; +} + +void RytyMetalUpdateBuffer(RytyMetalBufferHandle handle, const void* data, unsigned long length, unsigned long offset) { + (void)handle; (void)data; (void)length; (void)offset; +} + +void RytyMetalDestroyBuffer(RytyMetalBufferHandle handle) { + (void)handle; +} + +void RytyMetalSetViewport(double x, double y, double width, double height, double znear, double zfar) { + (void)x; (void)y; (void)width; (void)height; (void)znear; (void)zfar; +} + +void RytyMetalSetScissorRect(unsigned int x, unsigned int y, unsigned int width, unsigned int height) { + (void)x; (void)y; (void)width; (void)height; +} + +void RytyMetalSetBlendMode(int blendMode) { + (void)blendMode; +} + +void RytyMetalDrawPrimitives(int primitiveType, unsigned int start, unsigned int count) { + (void)primitiveType; (void)start; (void)count; +} + +void RytyMetalDrawIndexedPrimitives(int primitiveType, unsigned int indexCount, int indexType, RytyMetalBufferHandle indexBuffer, unsigned int indexBufferOffset) { + (void)primitiveType; (void)indexCount; (void)indexType; (void)indexBuffer; (void)indexBufferOffset; +} + +unsigned long RytyMetalGetMaxThreadsPerThreadgroup(void) { + return 1024; +} + +int RytyMetalSupportsFeatureSet(int featureSet) { + (void)featureSet; + return 1; +} diff --git a/core/libs/metal/RytyMetalView.mm b/core/libs/metal/RytyMetalView.mm index 4525e3a..4c644b9 100644 --- a/core/libs/metal/RytyMetalView.mm +++ b/core/libs/metal/RytyMetalView.mm @@ -57,3 +57,15 @@ bool InitializeWindow(int width, int height, const char* title) { extern "C" void RytyMetalViewHostEnableDepthStencil(int enabled) { RytyMetalSetDepthStencilEnabled(enabled); } + +extern "C" RytyMetalTextureHandle RytyMetalViewHostCreateTexture(int w, int h, int format) { + return RytyMetalCreateTexture2D(w, h, format); +} + +extern "C" void RytyMetalViewHostUpdateTexture(RytyMetalTextureHandle handle, const void* bytes, int bytesPerRow) { + RytyMetalUpdateTexture2D(handle, bytes, bytesPerRow); +} + +extern "C" void RytyMetalViewHostSetViewport(double x, double y, double w, double h) { + RytyMetalSetViewport(x, y, w, h, 0.0, 1.0); +} diff --git a/core/relinker/RelinkerTests.cpp b/core/relinker/RelinkerTests.cpp index e556ab1..3a2a395 100644 --- a/core/relinker/RelinkerTests.cpp +++ b/core/relinker/RelinkerTests.cpp @@ -11,6 +11,19 @@ int RytyMetalIsSupported(void); const char* RytyMetalGetDeviceName(void); void RytyMetalSetClearColor(double r, double g, double b, double a); void RytyMetalSetDepthStencilEnabled(int enabled); +void* RytyMetalCreateTexture2D(int width, int height, int pixelFormat); +void RytyMetalUpdateTexture2D(void* handle, const void* bytes, int bytesPerRow); +void RytyMetalDestroyTexture2D(void* handle); +void* RytyMetalCreateBuffer(const void* data, unsigned long length); +void RytyMetalUpdateBuffer(void* handle, const void* data, unsigned long length, unsigned long offset); +void RytyMetalDestroyBuffer(void* handle); +void RytyMetalSetViewport(double x, double y, double width, double height, double znear, double zfar); +void RytyMetalSetScissorRect(unsigned int x, unsigned int y, unsigned int width, unsigned int height); +void RytyMetalSetBlendMode(int blendMode); +void RytyMetalDrawPrimitives(int primitiveType, unsigned int start, unsigned int count); +void RytyMetalDrawIndexedPrimitives(int primitiveType, unsigned int indexCount, int indexType, void* indexBuffer, unsigned int indexBufferOffset); +unsigned long RytyMetalGetMaxThreadsPerThreadgroup(void); +int RytyMetalSupportsFeatureSet(int featureSet); } #include #include @@ -84,13 +97,38 @@ void TestMacOsPatcher() { // Mach-O MH_MAGIC_64 is 0xfeedfacf (0xcf, 0xfa, 0xed, 0xfe in little endian) assert(patched[0] == 0xcf && patched[1] == 0xfa && patched[2] == 0xed && patched[3] == 0xfe); - // Verify Metal bridge initialization functions + // Verify Mach-O header commands count (14 load commands) + std::uint32_t cmdsCount = 0; + std::memcpy(&cmdsCount, patched.data() + 16, 4); + assert(cmdsCount == 14); + + // Verify Metal bridge initialization and expanded C API functions RytyMetalInitializeDevice(); assert(RytyMetalIsSupported() != 0); assert(RytyMetalGetDeviceName() != nullptr); RytyMetalSetClearColor(0.2, 0.2, 0.2, 1.0); RytyMetalSetDepthStencilEnabled(1); + void* tex = RytyMetalCreateTexture2D(256, 256, 80); + assert(tex != nullptr); + uint32_t dummyPixel = 0xff0000ff; + RytyMetalUpdateTexture2D(tex, &dummyPixel, 4); + RytyMetalDestroyTexture2D(tex); + + void* buf = RytyMetalCreateBuffer(&dummyPixel, sizeof(dummyPixel)); + assert(buf != nullptr); + RytyMetalUpdateBuffer(buf, &dummyPixel, sizeof(dummyPixel), 0); + RytyMetalDestroyBuffer(buf); + + RytyMetalSetViewport(0, 0, 1920, 1080, 0.0, 1.0); + RytyMetalSetScissorRect(0, 0, 1920, 1080); + RytyMetalSetBlendMode(1); + RytyMetalDrawPrimitives(3, 0, 6); + RytyMetalDrawIndexedPrimitives(3, 6, 0, buf, 0); + + assert(RytyMetalGetMaxThreadsPerThreadgroup() > 0); + assert(RytyMetalSupportsFeatureSet(0) != 0); + std::cout << " -> PASSED!\n"; } diff --git a/core/relinker/elfpatcher/src/macos/MacOsElfPatcher.cpp b/core/relinker/elfpatcher/src/macos/MacOsElfPatcher.cpp index adabded..459b79b 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsElfPatcher.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsElfPatcher.cpp @@ -25,6 +25,50 @@ extern "C" void RytyMetalSetClearColor(double r, double g, double b, double a) { extern "C" void RytyMetalSetDepthStencilEnabled(int enabled) { (void)enabled; } +extern "C" void* RytyMetalCreateTexture2D(int width, int height, int pixelFormat) { + (void)width; (void)height; (void)pixelFormat; + static int dummyTexture = 1; + return &dummyTexture; +} +extern "C" void RytyMetalUpdateTexture2D(void* handle, const void* bytes, int bytesPerRow) { + (void)handle; (void)bytes; (void)bytesPerRow; +} +extern "C" void RytyMetalDestroyTexture2D(void* handle) { + (void)handle; +} +extern "C" void* RytyMetalCreateBuffer(const void* data, unsigned long length) { + (void)data; (void)length; + static int dummyBuffer = 1; + return &dummyBuffer; +} +extern "C" void RytyMetalUpdateBuffer(void* handle, const void* data, unsigned long length, unsigned long offset) { + (void)handle; (void)data; (void)length; (void)offset; +} +extern "C" void RytyMetalDestroyBuffer(void* handle) { + (void)handle; +} +extern "C" void RytyMetalSetViewport(double x, double y, double width, double height, double znear, double zfar) { + (void)x; (void)y; (void)width; (void)height; (void)znear; (void)zfar; +} +extern "C" void RytyMetalSetScissorRect(unsigned int x, unsigned int y, unsigned int width, unsigned int height) { + (void)x; (void)y; (void)width; (void)height; +} +extern "C" void RytyMetalSetBlendMode(int blendMode) { + (void)blendMode; +} +extern "C" void RytyMetalDrawPrimitives(int primitiveType, unsigned int start, unsigned int count) { + (void)primitiveType; (void)start; (void)count; +} +extern "C" void RytyMetalDrawIndexedPrimitives(int primitiveType, unsigned int indexCount, int indexType, void* indexBuffer, unsigned int indexBufferOffset) { + (void)primitiveType; (void)indexCount; (void)indexType; (void)indexBuffer; (void)indexBufferOffset; +} +extern "C" unsigned long RytyMetalGetMaxThreadsPerThreadgroup(void) { + return 1024; +} +extern "C" int RytyMetalSupportsFeatureSet(int featureSet) { + (void)featureSet; + return 1; +} #endif namespace Elfpatcher::MacOs { @@ -41,6 +85,8 @@ constexpr std::uint32_t LC_LOAD_DYLIB = 0x0c; constexpr std::uint32_t LC_LOAD_DYLINKER = 0x0e; constexpr std::uint32_t LC_MAIN = 0x80000028; constexpr std::uint32_t LC_RPATH = 0x8000001c; +constexpr std::uint32_t LC_BUILD_VERSION = 0x32; +constexpr std::uint32_t LC_CODE_SIGNATURE = 0x1d; struct MachOHeader64 { std::uint32_t Magic; @@ -83,12 +129,34 @@ struct DylibCommand { std::uint32_t CompatibilityVersion; }; +struct DylinkerCommand { + std::uint32_t Cmd; + std::uint32_t CmdSize; + std::uint32_t NameOffset; +}; + struct RpathCommand { std::uint32_t Cmd; std::uint32_t CmdSize; std::uint32_t PathOffset; }; +struct BuildVersionCommand { + std::uint32_t Cmd; + std::uint32_t CmdSize; + std::uint32_t Platform; + std::uint32_t MinOS; + std::uint32_t SDK; + std::uint32_t NTools; +}; + +struct LinkeditDataCommand { + std::uint32_t Cmd; + std::uint32_t CmdSize; + std::uint32_t DataOff; + std::uint32_t DataSize; +}; + std::vector makeDylibCommand(const std::string& path) { const std::size_t rawSize = sizeof(DylibCommand) + path.size() + 1; const std::size_t alignedSize = (rawSize + 7) & ~std::size_t(7); @@ -104,6 +172,18 @@ std::vector makeDylibCommand(const std::string& path) { return buf; } +std::vector makeDylinkerCommand(const std::string& path) { + const std::size_t rawSize = sizeof(DylinkerCommand) + path.size() + 1; + const std::size_t alignedSize = (rawSize + 7) & ~std::size_t(7); + std::vector buf(alignedSize, 0); + auto* cmd = reinterpret_cast(buf.data()); + cmd->Cmd = LC_LOAD_DYLINKER; + cmd->CmdSize = static_cast(alignedSize); + cmd->NameOffset = sizeof(DylinkerCommand); + std::memcpy(buf.data() + sizeof(DylinkerCommand), path.data(), path.size()); + return buf; +} + std::vector makeRpathCommand(const std::string& path) { const std::size_t rawSize = sizeof(RpathCommand) + path.size() + 1; const std::size_t alignedSize = (rawSize + 7) & ~std::size_t(7); @@ -116,6 +196,81 @@ std::vector makeRpathCommand(const std::string& path) { return buf; } +std::vector makeBuildVersionCommand() { + std::vector buf(sizeof(BuildVersionCommand), 0); + auto* cmd = reinterpret_cast(buf.data()); + cmd->Cmd = LC_BUILD_VERSION; + cmd->CmdSize = sizeof(BuildVersionCommand); + cmd->Platform = 1; // PLATFORM_MACOS + cmd->MinOS = 0x000B0000; // macOS 11.0 + cmd->SDK = 0x000E0000; // macOS 14.0 + cmd->NTools = 0; + return buf; +} + +std::vector makeCodeSignatureCommand(std::uint32_t offset, std::uint32_t size) { + std::vector buf(sizeof(LinkeditDataCommand), 0); + auto* cmd = reinterpret_cast(buf.data()); + cmd->Cmd = LC_CODE_SIGNATURE; + cmd->CmdSize = sizeof(LinkeditDataCommand); + cmd->DataOff = offset; + cmd->DataSize = size; + return buf; +} + +static void writeBe32(std::vector& buf, std::size_t offset, std::uint32_t val) { + buf[offset] = static_cast((val >> 24) & 0xff); + buf[offset + 1] = static_cast((val >> 16) & 0xff); + buf[offset + 2] = static_cast((val >> 8) & 0xff); + buf[offset + 3] = static_cast(val & 0xff); +} + +std::vector makeAdHocCodeSignatureBlob(std::size_t binarySize) { + const std::string identifier = "ryty.bundle"; + const std::uint32_t nCodeSlots = static_cast((binarySize + 4095) / 4096); + const std::uint32_t codeDirHeaderSize = 44; + const std::uint32_t hashOffset = codeDirHeaderSize + static_cast(identifier.size()) + 1; + const std::uint32_t hashSize = 20; // SHA-1 dummy hash + const std::uint32_t codeDirTotalSize = hashOffset + nCodeSlots * hashSize; + + const std::uint32_t superBlobHeaderSize = 12; + const std::uint32_t blobIndexSize = 8; + const std::uint32_t totalBlobSize = superBlobHeaderSize + blobIndexSize + codeDirTotalSize; + + std::vector blob(totalBlobSize, 0); + + // SuperBlob Header (big endian) + writeBe32(blob, 0, 0xfade0cc0); // CSMAGIC_EMBEDDED_SIGNATURE + writeBe32(blob, 4, totalBlobSize); + writeBe32(blob, 8, 1); // count = 1 + + // BlobIndex[0] + writeBe32(blob, 12, 0); // type = CS_CSSLOT_CODEDIRECTORY + writeBe32(blob, 16, superBlobHeaderSize + blobIndexSize); // offset to CodeDirectory + + // CodeDirectory Header + const std::size_t cdOff = superBlobHeaderSize + blobIndexSize; + writeBe32(blob, cdOff, 0xfade0c02); // CSMAGIC_CODEDIRECTORY + writeBe32(blob, cdOff + 4, codeDirTotalSize); + writeBe32(blob, cdOff + 8, 0x00020400); // version + writeBe32(blob, cdOff + 12, 0x00000002); // flags = CS_ADHOC + writeBe32(blob, cdOff + 16, hashOffset); + writeBe32(blob, cdOff + 20, codeDirHeaderSize); + writeBe32(blob, cdOff + 24, 0); // nSpecialSlots + writeBe32(blob, cdOff + 28, nCodeSlots); + writeBe32(blob, cdOff + 32, static_cast(binarySize)); // codeLimit + blob[cdOff + 36] = hashSize; // hashSize + blob[cdOff + 37] = 1; // hashType = CS_HASHTYPE_SHA1 + blob[cdOff + 38] = 0; // platform + blob[cdOff + 39] = 12; // pageSize (2^12 = 4096) + writeBe32(blob, cdOff + 40, 0); // spare1 + + // Copy identifier + std::memcpy(blob.data() + cdOff + codeDirHeaderSize, identifier.c_str(), identifier.size() + 1); + + return blob; +} + } MacOsElfPatcher::MacOsElfPatcher() = default; @@ -135,6 +290,9 @@ std::vector MacOsElfPatcher::Patch( // Invoke Metal device initialization symbol to link and initialize Metal API context RytyMetalInitializeDevice(); + const auto dylinkerCmd = makeDylinkerCommand("/usr/lib/dyld"); + const auto buildVerCmd = makeBuildVersionCommand(); + const auto metalLibCmd = makeDylibCommand("/System/Library/Frameworks/Metal.framework/Versions/A/Metal"); const auto metalKitLibCmd = makeDylibCommand("/System/Library/Frameworks/MetalKit.framework/Versions/A/MetalKit"); const auto cocoaLibCmd = makeDylibCommand("/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa"); @@ -144,10 +302,15 @@ std::vector MacOsElfPatcher::Patch( std::string rpath = runPath.empty() ? "@executable_path/libs" : runPath; const auto rpathCmd = makeRpathCommand(rpath); - const std::uint32_t cmdsCount = 9; + // Load Commands: + // PageZero, Text Segment, Data Segment, LinkEdit Segment, Main, Dylinker, BuildVersion, CodeSig, 5 Dylibs, Rpath + const std::uint32_t cmdsCount = 14; const std::uint32_t cmdsSize = static_cast( - sizeof(SegmentCommand64) * 2 + + sizeof(SegmentCommand64) * 4 + // PageZero, Text, Data, LinkEdit sizeof(MainCommand) + + dylinkerCmd.size() + + buildVerCmd.size() + + sizeof(LinkeditDataCommand) + metalLibCmd.size() + metalKitLibCmd.size() + cocoaLibCmd.size() + @@ -167,7 +330,7 @@ std::vector MacOsElfPatcher::Patch( header.Reserved = 0; std::vector result; - result.reserve(sizeof(header) + cmdsSize + 16 + sourceElf.size()); + result.reserve(sizeof(header) + cmdsSize + 16 + sourceElf.size() + 2048); const auto appendBytes = [&](const void* ptr, std::size_t size) { const auto* bytePtr = static_cast(ptr); @@ -231,19 +394,51 @@ std::vector MacOsElfPatcher::Patch( } const std::uint64_t textSize = entryStubSize + payload.size() + trampolineTotalSize; + const std::uint64_t textVSize = (textSize + 0xFFF) & ~0xFFFull; SegmentCommand64 textSeg{}; textSeg.Cmd = LC_SEGMENT_64; textSeg.CmdSize = sizeof(SegmentCommand64); std::strncpy(textSeg.SegName, "__TEXT", 16); textSeg.VAddr = 0x100000000ull; - textSeg.VSize = (textSize + 0xFFF) & ~0xFFFull; + textSeg.VSize = textVSize; textSeg.FileOff = headerAndCmdsSize; textSeg.FileSize = textSize; textSeg.MaxProt = 7; // VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE textSeg.InitProt = 5; // VM_PROT_READ | VM_PROT_EXECUTE appendBytes(&textSeg, sizeof(textSeg)); + // Data segment for writable memory + const std::uint64_t dataVAddr = 0x100000000ull + textVSize; + constexpr std::uint64_t dataSize = 0x1000; + SegmentCommand64 dataSeg{}; + dataSeg.Cmd = LC_SEGMENT_64; + dataSeg.CmdSize = sizeof(SegmentCommand64); + std::strncpy(dataSeg.SegName, "__DATA", 16); + dataSeg.VAddr = dataVAddr; + dataSeg.VSize = dataSize; + dataSeg.FileOff = headerAndCmdsSize + textSize; + dataSeg.FileSize = 0; // BSS style data + dataSeg.MaxProt = 3; // VM_PROT_READ | VM_PROT_WRITE + dataSeg.InitProt = 3; // VM_PROT_READ | VM_PROT_WRITE + appendBytes(&dataSeg, sizeof(dataSeg)); + + // LinkEdit segment for Code Signature + const std::uint64_t codeSigFileOff = headerAndCmdsSize + textSize; + const std::uint64_t codeSigBlobSize = makeAdHocCodeSignatureBlob(codeSigFileOff).size(); + + SegmentCommand64 linkeditSeg{}; + linkeditSeg.Cmd = LC_SEGMENT_64; + linkeditSeg.CmdSize = sizeof(SegmentCommand64); + std::strncpy(linkeditSeg.SegName, "__LINKEDIT", 16); + linkeditSeg.VAddr = dataVAddr + dataSize; + linkeditSeg.VSize = (codeSigBlobSize + 0xFFF) & ~0xFFFull; + linkeditSeg.FileOff = codeSigFileOff; + linkeditSeg.FileSize = codeSigBlobSize; + linkeditSeg.MaxProt = 1; // VM_PROT_READ + linkeditSeg.InitProt = 1; // VM_PROT_READ + appendBytes(&linkeditSeg, sizeof(linkeditSeg)); + MainCommand mainCmd{}; mainCmd.Cmd = LC_MAIN; mainCmd.CmdSize = sizeof(MainCommand); @@ -251,6 +446,12 @@ std::vector MacOsElfPatcher::Patch( mainCmd.StackSize = 0; appendBytes(&mainCmd, sizeof(mainCmd)); + appendBytes(dylinkerCmd.data(), dylinkerCmd.size()); + appendBytes(buildVerCmd.data(), buildVerCmd.size()); + + const auto codeSigCmd = makeCodeSignatureCommand(static_cast(codeSigFileOff), static_cast(codeSigBlobSize)); + appendBytes(codeSigCmd.data(), codeSigCmd.size()); + appendBytes(metalLibCmd.data(), metalLibCmd.size()); appendBytes(metalKitLibCmd.data(), metalKitLibCmd.size()); appendBytes(cocoaLibCmd.data(), cocoaLibCmd.size()); @@ -298,6 +499,10 @@ std::vector MacOsElfPatcher::Patch( result.insert(result.end(), payload.begin(), payload.end()); result.insert(result.end(), trampolineBuf.begin(), trampolineBuf.end()); + // Append ad-hoc code signature blob + const auto codeSigBlob = makeAdHocCodeSignatureBlob(codeSigFileOff); + result.insert(result.end(), codeSigBlob.begin(), codeSigBlob.end()); + return result; }