implement an all-swift approach for detecting JIT (adds support for detecting JIT on iOS 18.4+), adds an easter egg, and more

This commit is contained in:
Stossy11 2025-03-01 20:17:49 +11:00
parent 527ac3fb23
commit 8df465a959
15 changed files with 120 additions and 208 deletions

View File

@ -83,7 +83,7 @@
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = dotnet.xcconfig.example; sourceTree = "<group>"; };
5650564A2D2A758600C8BB1E /* dotnet.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = dotnet.xcconfig; sourceTree = "<group>"; };
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -222,7 +222,7 @@
4E80A9842CD6F54500029585 = {
isa = PBXGroup;
children = (
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */,
5650564A2D2A758600C8BB1E /* dotnet.xcconfig */,
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
4E80A98F2CD6F54500029585 /* MeloNX */,
4E80A9A02CD6F54700029585 /* MeloNXTests */,
@ -260,7 +260,7 @@
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
buildPhases = (
);
buildToolPath = "$(DOTNET_PATH)";
buildToolPath = /usr/local/share/dotnet/dotnet;
buildWorkingDirectory = "$(SRCROOT)/../..";
dependencies = (
);
@ -666,6 +666,12 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
@ -737,6 +743,18 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
@ -790,6 +808,12 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
@ -861,6 +885,18 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;

View File

@ -14,8 +14,6 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#import "utils.h"
#ifdef __cplusplus
extern "C" {

View File

@ -5,15 +5,49 @@
// Created by Stossy11 on 10/02/2025.
//
import Foundation
func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
var region: vm_address_t = vm_address_t(UInt(bitPattern: address))
var regionSize: vm_size_t = 0
var info = vm_region_basic_info_64()
var infoCount = mach_msg_type_number_t(MemoryLayout<vm_region_basic_info_64>.size / MemoryLayout<integer_t>.size)
var objectName: mach_port_t = UInt32(MACH_PORT_NULL)
let result = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(infoCount)) {
vm_region_64(mach_task_self_, &region, &regionSize, VM_REGION_BASIC_INFO_64, $0, &infoCount, &objectName)
}
}
if result != KERN_SUCCESS {
print("Failed to reach \(address)")
return false
}
return info.protection & VM_PROT_EXECUTE != 0
}
func isJITEnabled() -> Bool {
var flags: Int = 0
let pageSize = sysconf(_SC_PAGESIZE)
let code: [UInt32] = [0x52800540, 0xD65F03C0]
csops(getpid(), 0, &flags, sizeof(flags))
return (Int32(flags) & CS_DEBUGGED) != 0;
}
func sizeof<T>(_ value: T) -> Int {
return MemoryLayout<T>.size
guard let jitMemory = mmap(nil, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0), jitMemory != MAP_FAILED else {
return false
}
defer {
munmap(jitMemory, pageSize)
}
memcpy(jitMemory, code, code.count)
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
return false
}
let checkMem = checkMemoryPermissions(at: jitMemory)
return checkMem
}

View File

@ -1,27 +0,0 @@
#if __has_feature(modules)
@import UIKit;
@import Foundation;
#else
#import "UIKit/UIKit.h"
#import "Foundation/Foundation.h"
#endif
#define DISPATCH_ASYNC_START dispatch_async(dispatch_get_main_queue(), ^{
#define DISPATCH_ASYNC_CLOSE });
#define PT_TRACE_ME 0
extern int ptrace(int, pid_t, caddr_t, int);
#define CS_DEBUGGED 0x10000000
extern int csops(
pid_t pid,
unsigned int ops,
void *useraddr,
size_t usersize
);
extern BOOL getEntitlementValue(NSString *key);
extern BOOL isJITEnabled(void);
#define DLOG(format, ...) ShowAlert(@"DEBUG", [NSString stringWithFormat:@"\n %s [Line %d] \n %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:format, ##__VA_ARGS__]])
void ShowAlert(NSString* title, NSString* message, _Bool* showok);

View File

@ -1,82 +0,0 @@
#import "utils.h"
typedef struct __SecTask * SecTaskRef;
extern CFTypeRef SecTaskCopyValueForEntitlement(
SecTaskRef task,
NSString* entitlement,
CFErrorRef _Nullable *error
)
__attribute__((weak_import));
extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator)
__attribute__((weak_import));
BOOL getEntitlementValue(NSString *key)
{
if (SecTaskCreateFromSelf == NULL || SecTaskCopyValueForEntitlement == NULL)
return NO;
SecTaskRef sec_task = SecTaskCreateFromSelf(NULL);
if(!sec_task) return NO;
CFTypeRef value = SecTaskCopyValueForEntitlement(sec_task, key, nil);
if (value != nil)
{
CFRelease(value);
}
CFRelease(sec_task);
return value != nil && [(__bridge id)value boolValue];
}
BOOL isJITEnabled(void)
{
if (getEntitlementValue(@"dynamic-codesigning"))
{
return YES;
}
int flags;
csops(getpid(), 0, &flags, sizeof(flags));
return (flags & CS_DEBUGGED) != 0;
}
void ShowAlert(NSString* title, NSString* message, _Bool* showok)
{
DISPATCH_ASYNC_START
UIWindow* mainWindow = [[UIApplication sharedApplication] windows].lastObject;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
if (showok) {
[alert addAction:[UIAlertAction actionWithTitle:@"ok!"
style:UIAlertActionStyleDefault
handler:nil]];
}
[mainWindow.rootViewController presentViewController:alert
animated:true
completion:nil];
DISPATCH_ASYNC_CLOSE
}
#import <UIKit/UIKit.h>
__attribute__((constructor)) static void entry(int argc, char **argv)
{
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
NSLog(@"Entitlement Does Exist");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"increased-memory-limit"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.increased-debugging-memory-limit")) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"increased-debugging-memory-limit"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"extended-virtual-addressing"];
[defaults synchronize]; // Ensure the value is saved immediately
}
}

View File

@ -57,25 +57,17 @@ struct ContentView: View {
let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "")
_config = State(initialValue: defaultConfig)
let defaultSettings: [MoltenVKSettings] = [
// MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2"),
// Metal Private API isn't needed and causes more stutters
let defaultSettings: [MoltenVKSettings] = [ // Default MoltenVK Settings.
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
// MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "0"),
// MVK_CONFIG_LOG_LEVEL
//MVK_DEBUG
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64 or 192 depending on what i decide)
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64)
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "128"),
]
_settings = State(initialValue: defaultSettings)
print("JIT Enabled: \(isJITEnabled())")
initializeSDL()
}
@ -91,20 +83,6 @@ struct ContentView: View {
} else {
ZStack {
emulationView
.onAppear() {
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
/*
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
timer.invalidate()
quits = quit
if quits {
quit = false
timer.invalidate()
}
}
*/
}
}
}
} else {
@ -116,10 +94,11 @@ struct ContentView: View {
isAnimating = false
}
} else {
VStack {
}
EmulationView()
.persistentSystemOverlays(.hidden)
.onAppear() {
isAnimating = false
}
}
}
} else {
@ -197,14 +176,12 @@ struct ContentView: View {
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
ZStack(alignment: .leading) {
// Background track
Rectangle()
.cornerRadius(10)
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.gray.opacity(0.3))
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
// Animated loading bar
Rectangle()
.cornerRadius(10)
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
@ -272,15 +249,9 @@ struct ContentView: View {
))
let isJIT = isJITEnabled()
if !isJIT, useTrollStore {
askForJIT()
if !isJIT {
useTrollStore ? askForJIT() : enableJITEB()
}
if !isJIT, jitStreamerEB {
enableJITEB()
}
}
}
@ -289,7 +260,7 @@ struct ContentView: View {
private func initializeSDL() {
setMoltenVKSettings()
SDL_SetMainReady() // Sets SDL Ready
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true (Check out SDL2 Documentation here)
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true
SDL_Init(SdlInitFlags) // Initialises SDL2
initialize()
}
@ -327,27 +298,6 @@ struct ContentView: View {
}
}
func showAlert(title: String, message: String, showOk: Bool, completion: @escaping (Bool) -> Void) {
DispatchQueue.main.async {
if let mainWindow = UIApplication.shared.windows.last {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
if showOk {
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
completion(true)
}
alert.addAction(okAction)
} else {
completion(false)
}
mainWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}
}
private func start(displayid: UInt32) {

View File

@ -424,6 +424,8 @@ struct GameListRow: View {
@State var showGameDeleteConfirmation: Bool = false
@Environment(\.colorScheme) var colorScheme
@AppStorage("portal") var gamepo = false
var body: some View {
Button(action: {
startemu = game
@ -478,6 +480,12 @@ struct GameListRow: View {
Button {
gameInfo = game
isViewingGameInfo.toggle()
if game.titleName.lowercased() == "portal" {
gamepo = true
} else if game.titleName.lowercased() == "portal 2" {
gamepo = true
}
} label: {
Label("Game Info", systemImage: "info.circle")
}
@ -499,6 +507,7 @@ struct GameListRow: View {
}
}
Section {
Button(role: .destructive) {
gametoDelete = game

View File

@ -46,6 +46,7 @@ struct SettingsView: View {
@State private var showAnisotropicInfo = false
@State private var showControllerInfo = false
@State private var searchText = ""
@AppStorage("portal") var gamepo = false
var filteredMemoryModes: [(String, String)] {
guard !searchText.isEmpty else { return memoryManagerModes }
@ -391,7 +392,7 @@ struct SettingsView: View {
.onAppear() {
print("CPU Info: \(cpuInfo)")
}
} else if getEntitlementValue("com.apple.private.hypervisor") {
} else if checkAppEntitlement("com.apple.private.hypervisor") {
Toggle(isOn: $config.hypervisor) {
labelWithIcon("Hypervisor", iconName: "bolt")
}
@ -530,12 +531,10 @@ struct SettingsView: View {
// Advanced
Section {
if #unavailable(iOS 17) {
Toggle(isOn: $windowCode) {
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
}
.tint(.blue)
Toggle(isOn: $windowCode) {
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
}
.tint(.blue)
DisclosureGroup {
@ -588,9 +587,9 @@ struct SettingsView: View {
.headerProminence(.increased)
} footer: {
if #available(iOS 17, *) {
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing).")
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). \n \n\(gamepo ? "the cake is a lie" : "")")
} else {
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). If the emulation is not showing (you may hear audio in some games), try enabling \"SDL Window\"")
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). If the emulation is not showing (you may hear audio in some games), try enabling \"SDL Window\" \n \n\(gamepo ? "the cake is a lie" : "")")
}
}

View File

@ -118,10 +118,10 @@ struct UpdateManagerSheet: View {
Ryujinx.shared.games = Ryujinx.shared.loadGames()
}
func saveJSON(selectedItem: String) {
func saveJSON(selectedItem: String?) {
guard let jsonURL = jsonURL else { return }
do {
let jsonDict = ["paths": items, "selected": selectedItem] as [String: Any]
let jsonDict = ["paths": items, "selected": selectedItem ?? self.selectedItem ?? ""] as [String: Any]
let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
try newData.write(to: jsonURL)
} catch {

View File

@ -1,11 +0,0 @@
//
// dotnet.xcconfig
// MeloNX
//
// Created by June P on 12/25/24.
//
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
DOTNET_PATH = $(HOME)/.dotnet/dotnet

View File

@ -26,10 +26,16 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
return $"lib{libraryName}.so.{version}";
}
else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) // TODO: ffmpeg on ios
else if (OperatingSystem.IsMacOS())
{
return $"lib{libraryName}.{version}.dylib";
}
else if (OperatingSystem.IsIOS())
{
string libName = $"lib{libraryName}.{version}.dylib";
Console.WriteLine($"[iOS] Required firmware library: {libName}");
return libName;
}
else
{
throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}");

View File

@ -9,10 +9,10 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
[SupportedOSPlatform("ios")]
public static partial class MVKInitialization
{
[LibraryImport("MoltenVK.framework/MoltenVK")]
[LibraryImport("libMoltenVK.dylib")]
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
[LibraryImport("MoltenVK.framework/MoltenVK")]
[LibraryImport("libMoltenVK.dylib")]
private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
public static void Initialize()

View File

@ -601,7 +601,7 @@ namespace Ryujinx.Graphics.Vulkan
if (supportsExtDynamicState)
{
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
// dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
}
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo

View File

@ -319,7 +319,7 @@ namespace Ryujinx.Headless.SDL2
var result = Parser.Default.ParseArguments<Options>(args)
.WithParsed(options =>
{
Load(options); // Load is called with the parsed options
Load(options);
})
.WithNotParsed(errors => errors.Output());