A lot of backend changes, New Display UI, StikDebug 2.0 support, TXM Checker, Get Device name from MGCopyAnswer instead of an array and so on

This commit is contained in:
Stossy11 2025-07-14 15:33:36 +10:00
parent b575c61907
commit a2229109ca
57 changed files with 2512 additions and 5458 deletions

View File

@ -47,13 +47,13 @@ namespace ARMeilleure.CodeGen.X86
0xc3, // ret 0xc3, // ret
}; };
using MemoryBlock memGetXcr0 = new((ulong)asmGetXcr0.Length); using MemoryBlock memGetXcr0 = new((ulong)asmGetXcr0.Length, MemoryAllocationFlags.DualMapping);
memGetXcr0.Write(0, asmGetXcr0); memGetXcr0.Write(0, asmGetXcr0);
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute); memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer); var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.RxPointer);
return fGetXcr0(); return fGetXcr0();
} }

View File

@ -46,13 +46,6 @@
remoteGlobalIDString = 4E80A98C2CD6F54500029585; remoteGlobalIDString = 4E80A98C2CD6F54500029585;
remoteInfo = MeloNX; remoteInfo = MeloNX;
}; };
4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
proxyType = 1;
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
};
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = { BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */; containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
@ -109,6 +102,10 @@
CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = { CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet; isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = { attributesByRelativePath = {
"Dependencies/Dynamic Libraries/BreakpointJIT.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/Hypervisor.framework" = ( "Dependencies/Dynamic Libraries/Hypervisor.framework" = (
CodeSignOnCopy, CodeSignOnCopy,
RemoveHeadersOnCopy, RemoveHeadersOnCopy,
@ -120,10 +117,6 @@
CodeSignOnCopy, CodeSignOnCopy,
RemoveHeadersOnCopy, RemoveHeadersOnCopy,
); );
"Dependencies/Dynamic Libraries/StosJIT.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = ( "Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (
CodeSignOnCopy, CodeSignOnCopy,
); );
@ -172,13 +165,13 @@
}; };
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */; buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
membershipExceptions = ( membershipExceptions = (
"Dependencies/Dynamic Libraries/BreakpointJIT.framework",
"Dependencies/Dynamic Libraries/Hypervisor.framework", "Dependencies/Dynamic Libraries/Hypervisor.framework",
"Dependencies/Dynamic Libraries/libavcodec.dylib", "Dependencies/Dynamic Libraries/libavcodec.dylib",
"Dependencies/Dynamic Libraries/libavutil.dylib", "Dependencies/Dynamic Libraries/libavutil.dylib",
"Dependencies/Dynamic Libraries/libMoltenVK.dylib", "Dependencies/Dynamic Libraries/libMoltenVK.dylib",
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib", "Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
"Dependencies/Dynamic Libraries/RyujinxHelper.framework", "Dependencies/Dynamic Libraries/RyujinxHelper.framework",
"Dependencies/Dynamic Libraries/StosJIT.framework",
Dependencies/XCFrameworks/libavcodec.xcframework, Dependencies/XCFrameworks/libavcodec.xcframework,
Dependencies/XCFrameworks/libavfilter.xcframework, Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework, Dependencies/XCFrameworks/libavformat.xcframework,
@ -294,7 +287,6 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */,
); );
fileSystemSynchronizedGroups = ( fileSystemSynchronizedGroups = (
4E80A98F2CD6F54500029585 /* MeloNX */, 4E80A98F2CD6F54500029585 /* MeloNX */,
@ -492,11 +484,6 @@
target = 4E80A98C2CD6F54500029585 /* MeloNX */; target = 4E80A98C2CD6F54500029585 /* MeloNX */;
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */; targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
}; };
4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
targetProxy = 4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */;
};
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = { BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */; target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
@ -791,6 +778,10 @@
"$(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",
); );
GCC_OPTIMIZATION_LEVEL = z; GCC_OPTIMIZATION_LEVEL = z;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -1046,6 +1037,10 @@
"$(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 = "$(VERSION)"; MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
@ -1207,6 +1202,10 @@
"$(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",
); );
GCC_OPTIMIZATION_LEVEL = z; GCC_OPTIMIZATION_LEVEL = z;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -1462,6 +1461,10 @@
"$(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 = "$(VERSION)"; MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;

View File

@ -65,6 +65,7 @@
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugXPCServices = "NO" debugXPCServices = "NO"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUFrameCaptureMode = "3"
enableGPUValidationMode = "1" enableGPUValidationMode = "1"
allowLocationSimulation = "YES" allowLocationSimulation = "YES"
queueDebuggingEnabled = "No" queueDebuggingEnabled = "No"

View File

@ -0,0 +1,52 @@
//
// MobileGestalt.h
// MeloNX
//
// Created by Stossy11 on 11/07/2025.
//
/*
* libMobileGestalt header.
* Mobile gestalt functions as a QA system. You ask it a question, and it gives you the answer! :)
*
* Copyright (c) 2013-2014 Cykey (David Murray)
* Improved by @PoomSmart (2020)
* All rights reserved.
*/
#ifndef LIBMOBILEGESTALT_H_
#define LIBMOBILEGESTALT_H_
#include <dlfcn.h>
#include <CoreFoundation/CoreFoundation.h>
#if __cplusplus
extern "C" {
#endif
#pragma mark - API
typedef CFPropertyListRef (*MGFuncType)(CFStringRef);
CFPropertyListRef CallMGCopyAnswer(CFStringRef key) {
static MGFuncType fn = NULL;
if (!fn) {
void *handle = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_LAZY);
if (!handle) return NULL;
char decoded[] = { 'M','G','C','o','p','y','A','n','s','w','e','r', '\0' };
fn = (MGFuncType)dlsym(handle, decoded);
}
return fn ? fn(key) : NULL;
}
#pragma mark - Device Information
static const CFStringRef kMGPhysicalHardwareNameString = CFSTR("PhysicalHardwareNameString");
#if __cplusplus
}
#endif
#endif /* LIBMOBILEGESTALT_H_ */

View File

@ -14,8 +14,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h> #include <SDL2/SDL_syswm.h>
#include <StosJIT/StosJIT-Swift.h> #include "MobileGestalt.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {

View File

@ -17,7 +17,11 @@ func isJITEnabled() -> Bool {
return allocateTest() return allocateTest()
} }
return csops(pid: getpid(), ops: 0, useraddr: &flags, usersize: Int32(MemoryLayout.size(ofValue: flags))) == 0 && (flags & Int(CS_DEBUGGED)) != 0 ? allocateTest() : false if #available(iOS 19, *) {
return checkDebugged()
} else {
return checkDebugged() && allocateTest()
}
} }
func checkDebugged() -> Bool { func checkDebugged() -> Bool {
@ -70,3 +74,26 @@ func allocateTest() -> Bool {
return checkMem return checkMem
} }
// thank you nikki (nythepegasus)
extension FileManager {
func filePath(atPath path: String, withLength length: Int) -> String? {
guard let file = try? contentsOfDirectory(atPath: path).filter({ $0.count == length }).first else { return nil }
return "\(path)/\(file)"
}
}
func notnil(_ condition: Any?) -> Bool {
if let _ = condition {
return false
} else {
return true
}
}
public extension ProcessInfo {
var hasTXM: Bool {
{ if let boot = FileManager.default.filePath(atPath: "/System/Volumes/Preboot", withLength: 36), let file = FileManager.default.filePath(atPath: "\(boot)/boot", withLength: 96) { return access("\(file)/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4", F_OK) == 0 } else { return (FileManager.default.filePath(atPath: "/private/preboot", withLength: 96).map { access("\($0)/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4", F_OK) == 0 }) ?? false } }()
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
//
// ControllerInfo.swift
// MeloNX
//
// Created by Stossy11 on 28/06/2025.
//
struct Controller: Identifiable, Hashable {
var id: String
var name: String
}

View File

@ -12,7 +12,8 @@ class NativeController: Hashable, BaseController {
private var instanceID: SDL_JoystickID = -1 private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer? private var controller: OpaquePointer?
private var nativeController: GCController private var nativeController: GCController
private var controllerMotionProvider: DSUMotionProvider? private var controllerMotionProvider: ControllerMotionProvider?
private var deviceMotionProvider: DeviceMotionProvider?
private let controllerHaptics: CHHapticEngine? private let controllerHaptics: CHHapticEngine?
private let rumbleController: RumbleController? private let rumbleController: RumbleController?
@ -24,14 +25,14 @@ class NativeController: Hashable, BaseController {
var ncontrollerHaptics = nativeController.haptics?.createEngine(withLocality: .default) var ncontrollerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
let vendorName = nativeController.vendorName ?? "Unknown" let vendorName = nativeController.vendorName ?? "Unknown"
var usesdeviceHaptics = (ncontrollerHaptics == nil || vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one") var usesdeviceHaptics = (ncontrollerHaptics == nil && (vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one"))
controllerHaptics = usesdeviceHaptics ? ncontrollerHaptics : try? CHHapticEngine() controllerHaptics = usesdeviceHaptics ? try? CHHapticEngine() : ncontrollerHaptics
// Make sure the haptic engine exists before attempting to start it or initialize the controller. // Make sure the haptic engine exists before attempting to start it or initialize the controller.
if let hapticsEngine = controllerHaptics { if let hapticsEngine = controllerHaptics {
do { do {
try hapticsEngine.start() try hapticsEngine.start()
rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 1.2) rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: usesdeviceHaptics ? 2.0 : 2.5)
} catch { } catch {
rumbleController = nil rumbleController = nil
} }
@ -51,14 +52,22 @@ class NativeController: Hashable, BaseController {
let vendorName = nativeController.vendorName ?? "Unknown" let vendorName = nativeController.vendorName ?? "Unknown"
var usesdevicemotion = (vendorName.lowercased() == "Joy-Con (l/R)".lowercased() || vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one") var usesdevicemotion = (vendorName.lowercased() == "Joy-Con (l/R)".lowercased() || vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one")
controllerMotionProvider = usesdevicemotion ? DeviceMotionProvider(slot: slot) : ControllerMotionProvider(controller: nativeController, slot: slot) usesdevicemotion ? (deviceMotionProvider = DeviceMotionProvider(slot: slot)) : (controllerMotionProvider = ControllerMotionProvider(controller: nativeController, slot: slot))
if let provider = controllerMotionProvider { if let provider = controllerMotionProvider {
dsuServer.register(provider) dsuServer.register(provider)
} else if let provider = deviceMotionProvider {
dsuServer.register(provider)
} }
} }
internal func tryGetMotionProvider() -> DSUMotionProvider? { return controllerMotionProvider } internal func tryGetMotionProvider() -> DSUMotionProvider? {
if let deviceMotionProvider {
return deviceMotionProvider
}
return controllerMotionProvider
}
private func setupHandheldController() { private func setupHandheldController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 { if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {

View File

@ -158,7 +158,7 @@ class VirtualController : BaseController {
} }
} }
enum VirtualControllerButton: Int { enum VirtualControllerButton: Int, Codable {
case A case A
case B case B
case X case X
@ -196,7 +196,7 @@ enum VirtualControllerButton: Int {
} }
} }
enum ThumbstickType: Int { enum ThumbstickType: Int, Codable {
case left case left
case right case right
} }

View File

@ -99,10 +99,7 @@ extension Notification.Name {
static let newLogCaptured = Notification.Name("newLogCaptured") static let newLogCaptured = Notification.Name("newLogCaptured")
} }
struct Controller: Identifiable, Hashable {
var id: String
var name: String
}
struct iOSNav<Content: View>: View { struct iOSNav<Content: View>: View {
@ViewBuilder var content: () -> Content @ViewBuilder var content: () -> Content
@ -136,6 +133,7 @@ class Ryujinx : ObservableObject {
@Published var emulationUIView: MeloMTKView? = nil @Published var emulationUIView: MeloMTKView? = nil
@Published var config: Ryujinx.Arguments? = nil @Published var config: Ryujinx.Arguments? = nil
@Published var games: [Game] = [] @Published var games: [Game] = []
@Published var aspectRatio: AspectRatio = .fixed16x9
@Published var defMLContentSize: CGFloat? @Published var defMLContentSize: CGFloat?
@ -240,7 +238,7 @@ class Ryujinx : ObservableObject {
disablevsync: Bool = false, disablevsync: Bool = false,
language: SystemLanguage = .americanEnglish, language: SystemLanguage = .americanEnglish,
regioncode: SystemRegionCode = .usa, regioncode: SystemRegionCode = .usa,
handHeldController: Bool = false, handHeldController: Bool = false
) { ) {
self.gamepath = gamepath self.gamepath = gamepath
self.inputids = inputids self.inputids = inputids
@ -424,6 +422,42 @@ class Ryujinx : ObservableObject {
} }
} }
static func clearShaderCache(_ titleId: String = "") {
showAlert(title: "Clear Shader Cache", message: titleId.isEmpty ? "Are you sure you want to clear ALL shader cache?" : "Are you sure you want to clear your shader cache?",
actions: [
(title: "Cancel", style: .cancel, handler: nil),
(title: "Clear", style: .destructive, handler: {
if titleId.isEmpty {
let fileManager = FileManager.default
let gamesURL = URL.documentsDirectory.appendingPathComponent("games")
do {
let contents = try fileManager.contentsOfDirectory(at: gamesURL, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles])
let folderURLs = contents.filter { url in
(try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true
}
for folderURL in folderURLs {
try? fileManager.removeItem(at: folderURL.appendingPathComponent("cache"))
}
} catch {
print("Error reading games folder: \(error)")
}
} else {
let fileManager = FileManager.default
let cacheURL = URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(titleId).appendingPathComponent("cache")
try? fileManager.removeItem(at: cacheURL)
}
}),
]
)
}
struct ExceptionInfo { struct ExceptionInfo {
let exceptionType: String let exceptionType: String
let message: String let message: String
@ -563,7 +597,11 @@ class Ryujinx : ObservableObject {
args.append(contentsOf: ["--system-region", config.regioncode.rawValue]) args.append(contentsOf: ["--system-region", config.regioncode.rawValue])
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue]) DispatchQueue.main.async {
self.aspectRatio = config.aspectRatio
}
args.append(contentsOf: ["--aspect-ratio", "Stretched"])
args.append(contentsOf: ["--system-timezone", TimeZone.current.identifier]) args.append(contentsOf: ["--system-timezone", TimeZone.current.identifier])
@ -835,116 +873,8 @@ public extension UIDevice {
return identifier + String(UnicodeScalar(UInt8(value))) return identifier + String(UnicodeScalar(UInt8(value)))
} }
func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity return CallMGCopyAnswer(kMGPhysicalHardwareNameString)?.takeUnretainedValue() as? String ?? identifier
#if os(iOS)
switch identifier {
case "iPod5,1": return "iPod touch (5th generation)"
case "iPod7,1": return "iPod touch (6th generation)"
case "iPod9,1": return "iPod touch (7th generation)"
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4"
case "iPhone4,1": return "iPhone 4s"
case "iPhone5,1", "iPhone5,2": return "iPhone 5"
case "iPhone5,3", "iPhone5,4": return "iPhone 5c"
case "iPhone6,1", "iPhone6,2": return "iPhone 5s"
case "iPhone7,2": return "iPhone 6"
case "iPhone7,1": return "iPhone 6 Plus"
case "iPhone8,1": return "iPhone 6s"
case "iPhone8,2": return "iPhone 6s Plus"
case "iPhone9,1", "iPhone9,3": return "iPhone 7"
case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus"
case "iPhone10,1", "iPhone10,4": return "iPhone 8"
case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus"
case "iPhone10,3", "iPhone10,6": return "iPhone X"
case "iPhone11,2": return "iPhone XS"
case "iPhone11,4", "iPhone11,6": return "iPhone XS Max"
case "iPhone11,8": return "iPhone XR"
case "iPhone12,1": return "iPhone 11"
case "iPhone12,3": return "iPhone 11 Pro"
case "iPhone12,5": return "iPhone 11 Pro Max"
case "iPhone13,1": return "iPhone 12 mini"
case "iPhone13,2": return "iPhone 12"
case "iPhone13,3": return "iPhone 12 Pro"
case "iPhone13,4": return "iPhone 12 Pro Max"
case "iPhone14,4": return "iPhone 13 mini"
case "iPhone14,5": return "iPhone 13"
case "iPhone14,2": return "iPhone 13 Pro"
case "iPhone14,3": return "iPhone 13 Pro Max"
case "iPhone14,7": return "iPhone 14"
case "iPhone14,8": return "iPhone 14 Plus"
case "iPhone15,2": return "iPhone 14 Pro"
case "iPhone15,3": return "iPhone 14 Pro Max"
case "iPhone15,4": return "iPhone 15"
case "iPhone15,5": return "iPhone 15 Plus"
case "iPhone16,1": return "iPhone 15 Pro"
case "iPhone16,2": return "iPhone 15 Pro Max"
case "iPhone17,3": return "iPhone 16"
case "iPhone17,4": return "iPhone 16 Plus"
case "iPhone17,1": return "iPhone 16 Pro"
case "iPhone17,2": return "iPhone 16 Pro Max"
case "iPhone17,5": return "iPhone 16e"
case "iPhone8,4": return "iPhone SE"
case "iPhone12,8": return "iPhone SE (2nd generation)"
case "iPhone14,6": return "iPhone SE (3rd generation)"
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "iPad 2"
case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)"
case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)"
case "iPad6,11", "iPad6,12": return "iPad (5th generation)"
case "iPad7,5", "iPad7,6": return "iPad (6th generation)"
case "iPad7,11", "iPad7,12": return "iPad (7th generation)"
case "iPad11,6", "iPad11,7": return "iPad (8th generation)"
case "iPad12,1", "iPad12,2": return "iPad (9th generation)"
case "iPad13,18", "iPad13,19": return "iPad (10th generation)"
case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air"
case "iPad5,3", "iPad5,4": return "iPad Air 2"
case "iPad11,3", "iPad11,4": return "iPad Air (3rd generation)"
case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)"
case "iPad13,16", "iPad13,17": return "iPad Air (5th generation)"
case "iPad14,8", "iPad14,9": return "iPad Air (11-inch) (M2)"
case "iPad14,10", "iPad14,11": return "iPad Air (13-inch) (M2)"
case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad mini"
case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad mini 2"
case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad mini 3"
case "iPad5,1", "iPad5,2": return "iPad mini 4"
case "iPad11,1", "iPad11,2": return "iPad mini (5th generation)"
case "iPad14,1", "iPad14,2": return "iPad mini (6th generation)"
case "iPad16,1", "iPad16,2": return "iPad mini (A17 Pro)"
case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)"
case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)"
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": return "iPad Pro (11-inch) (1st generation)"
case "iPad8,9", "iPad8,10": return "iPad Pro (11-inch) (2nd generation)"
case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": return "iPad Pro (11-inch) (3rd generation)"
case "iPad14,3", "iPad14,4": return "iPad Pro (11-inch) (4th generation)"
case "iPad16,3", "iPad16,4": return "iPad Pro (11-inch) (M4)"
case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch) (1st generation)"
case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)"
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": return "iPad Pro (12.9-inch) (3rd generation)"
case "iPad8,11", "iPad8,12": return "iPad Pro (12.9-inch) (4th generation)"
case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11":return "iPad Pro (12.9-inch) (5th generation)"
case "iPad14,5", "iPad14,6": return "iPad Pro (12.9-inch) (6th generation)"
case "iPad16,5", "iPad16,6": return "iPad Pro (13-inch) (M4)"
case "AppleTV5,3": return "Apple TV"
case "AppleTV6,2": return "Apple TV 4K"
case "AudioAccessory1,1": return "HomePod"
case "AudioAccessory5,1": return "HomePod mini"
case "i386", "x86_64", "arm64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))"
default: return identifier
}
#elseif os(tvOS)
switch identifier {
case "AppleTV5,3": return "Apple TV 4"
case "AppleTV6,2", "AppleTV11,1", "AppleTV14,1": return "Apple TV 4K"
case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))"
default: return identifier
}
#elseif os(visionOS)
switch identifier {
case "RealityDevice14,1": return "Apple Vision Pro"
default: return identifier
}
#endif
}
return mapToDevice(identifier: identifier)
}() }()
} }

View File

@ -1,27 +0,0 @@
//
// ToggleButtonsState.swift
// MeloNX
//
// Created by Stossy11 on 12/04/2025.
//
struct ToggleButtonsState: Codable, Equatable {
var toggle1: Bool
var toggle2: Bool
var toggle3: Bool
var toggle4: Bool
init() {
self = .default
}
init(toggle1: Bool, toggle2: Bool, toggle3: Bool, toggle4: Bool) {
self.toggle1 = toggle1
self.toggle2 = toggle2
self.toggle3 = toggle3
self.toggle4 = toggle4
}
static let `default` = ToggleButtonsState(toggle1: false, toggle2: false, toggle3: false, toggle4: false)
}

View File

@ -0,0 +1,27 @@
//
// Alerts.swift
// MeloNX
//
// Created by Stossy11 on 04/07/2025.
//
import UIKit
func showAlert(_ viewController: UIViewController? = nil,
title: String?,
message: String?,
actions: [(title: String, style: UIAlertAction.Style, handler: (() -> Void)?)]) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for action in actions {
let uiAction = UIAlertAction(title: action.title, style: action.style) { _ in
action.handler?()
}
alert.addAction(uiAction)
}
let coolVC = viewController ?? UIApplication.shared.windows.first?.rootViewController!
coolVC!.present(alert, animated: true, completion: nil)
}

View File

@ -10,22 +10,23 @@ import SwiftUI
struct Joystick: View { struct Joystick: View {
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0 @AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
@State var right = true
@Binding var position: CGPoint @Binding var position: CGPoint
@State var joystickSize: CGFloat @State var joystickSize: CGFloat
var boundarySize: CGFloat var boundarySize: CGFloat
@State private var offset: CGSize = .zero @State private var offset: CGSize = .zero
@Binding var showBackground: Bool @Binding var showBackground: Bool
@State var joystickSmallSize = false
let sensitivity: CGFloat = 1.2 let sensitivity: CGFloat = 1.2
var dragGesture: some Gesture { var dragGesture: some Gesture {
DragGesture() DragGesture()
.onChanged { value in .onChanged { value in
withAnimation(.easeIn) { withAnimation(.easeIn) {
showBackground = true showBackground = true
joystickSmallSize = true
} }
let translation = value.translation let translation = value.translation
@ -44,16 +45,28 @@ struct Joystick: View {
x: max(-1, min(1, (offset.width / extendedRadius) * sensitivity)), x: max(-1, min(1, (offset.width / extendedRadius) * sensitivity)),
y: max(-1, min(1, (offset.height / extendedRadius) * sensitivity)) y: max(-1, min(1, (offset.height / extendedRadius) * sensitivity))
) )
setPos()
} }
.onEnded { _ in .onEnded { _ in
offset = .zero offset = .zero
position = .zero position = .zero
setPos()
withAnimation(.easeOut) { withAnimation(.easeOut) {
showBackground = false showBackground = false
joystickSmallSize = false
} }
} }
} }
func setPos() {
if right {
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: position.x, y: position.y)
} else {
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: position.x, y: position.y)
}
}
var body: some View { var body: some View {
ZStack { ZStack {
Circle() Circle()
@ -82,7 +95,7 @@ struct Joystick: View {
.scaleEffect(controllerScale) .scaleEffect(controllerScale)
} }
.frame(width: boundarySize, height: boundarySize) .frame(width: boundarySize, height: boundarySize)
.onChange(of: showBackground) { newValue in .onChange(of: joystickSmallSize) { newValue in
if newValue { if newValue {
joystickSize *= 1.4 joystickSize *= 1.4
} else { } else {

View File

@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
struct JoystickController: View { struct JoystickController: View {
@State var iscool: Bool? = nil @State var iscool: Bool
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@Binding var showBackground: Bool @Binding var showBackground: Bool
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0 @AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
@ -25,15 +25,8 @@ struct JoystickController: View {
} }
public var body: some View { public var body: some View {
VStack { Group {
Joystick(position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground) Joystick(right: iscool, position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground)
.onChange(of: position) { newValue in
if iscool != nil {
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
} else {
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
}
}
} }
} }
} }

View File

@ -16,8 +16,7 @@ struct EmulationView: View {
@AppStorage("On-ScreenControllerOpacity") var controllerOpacity: Double = 1.0 @AppStorage("On-ScreenControllerOpacity") var controllerOpacity: Double = 1.0
@AppStorage("disableTouch") var blackScreen = false @AppStorage("OldView") var oldView = true
@State var isPresentedThree: Bool = false @State var isPresentedThree: Bool = false
@State var isAirplaying = Air.shared.connected @State var isAirplaying = Air.shared.connected
@Binding var startgame: Game? @Binding var startgame: Game?
@ -27,9 +26,18 @@ struct EmulationView: View {
@State var showSettings = false @State var showSettings = false
@State var pauseEmu = true @State var pauseEmu = true
@AppStorage("location-enabled") var locationenabled: Bool = false @AppStorage("location-enabled") var locationenabled: Bool = false
@FocusState private var isFocused: Bool
@ObservedObject var ryujijnx = Ryujinx.shared
var body: some View { var body: some View {
ZStack { ZStack {
if oldView {
Color.black
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
.allowsHitTesting(false)
}
if isAirplaying { if isAirplaying {
TouchView() TouchView()
.ignoresSafeArea() .ignoresSafeArea()
@ -37,104 +45,100 @@ struct EmulationView: View {
.onAppear { .onAppear {
Air.play(AnyView(MetalView().ignoresSafeArea().edgesIgnoringSafeArea(.all))) Air.play(AnyView(MetalView().ignoresSafeArea().edgesIgnoringSafeArea(.all)))
} }
Color.black
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
.allowsHitTesting(false)
} else { } else {
MetalView() // The Emulation View MetalViewContainer() // The Emulation View
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
} }
// Above Emulation View // Above Emulation View
if isVCA { if isVCA {
ControllerView() // Virtual Controller ControllerView(isEditing: .constant(false), gameId: startgame?.titleId) // Virtual Controller
.opacity(controllerOpacity) .opacity(controllerOpacity)
.allowsHitTesting(true) .allowsHitTesting(true)
} }
Group {
VStack { VStack {
HStack { HStack {
if performacehud, !showlogsgame { if !performacehud, showlogsgame, ProcessInfo.processInfo.isLowPowerModeEnabled {
PerformanceOverlayView() Circle()
} .fill(Color.orange)
.frame(width: 10, height: 10)
Spacer()
if performacehud, showlogsgame {
PerformanceOverlayView()
}
}
HStack {
if showlogsgame, get_current_fps() != 0 {
LogFileView(isfps: false)
}
Spacer()
}
if ssb {
HStack {
Menu {
/*
Button {
showSettings.toggle()
} label: {
Label {
Text("Game Settings")
} icon: {
Image(systemName: "gearshape.circle")
}
}
*/
Button {
pause_emulation(pauseEmu)
pauseEmu.toggle()
} label: {
Label {
Text(pauseEmu ? "Pause" : "Play")
} icon: {
Image(systemName: pauseEmu ? "pause.circle" : "play.circle")
}
}
Button(role: .destructive) {
startgame = nil
stop_emulation()
try? Ryujinx.shared.stop()
} label: {
Label {
Text("Exit (Unstable)")
} icon: {
Image(systemName: "x.circle")
}
}
} label: {
ExtButtonIconView(button: .guide, opacity: 0.4)
}
.padding() .padding()
Spacer()
}
} }
if ssb {
Menu {
Button {
pause_emulation(pauseEmu)
pauseEmu.toggle()
} label: {
Label {
Text(pauseEmu ? "Pause" : "Play")
} icon: {
Image(systemName: pauseEmu ? "pause.circle" : "play.circle")
}
}
Button {
// ryujijnx.config?.aspectRatio
ryujijnx.aspectRatio = nextAspectRatio(current: ryujijnx.aspectRatio)
} label: {
Label {
Text(ryujijnx.aspectRatio.displayName)
} icon: {
Image(systemName: "rectangle.expand.vertical")
}
}
Button(role: .destructive) {
startgame = nil
stop_emulation()
try? Ryujinx.shared.stop()
} label: {
Label {
Text("Exit (Unstable)")
} icon: {
Image(systemName: "x.circle")
}
}
} label: {
ExtButtonIconView(button: .guide, opacity: 0.4)
}
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
.padding()
}
Spacer() Spacer()
if performacehud, getenv("MTL_HUD_ENABLED").flatMap({ String(cString: $0) }) != "1" {
PerformanceOverlayView()
.opacity(controllerOpacity)
.padding(.horizontal)
}
}
Spacer()
}
if showlogsgame, get_current_fps() != 0 {
VStack {
LogFileView(isfps: false)
Spacer()
} }
} }
} }
.onAppear { .onAppear {
DispatchQueue.main.async {
isFocused = true
}
LocationManager.sharedInstance.startUpdatingLocation() LocationManager.sharedInstance.startUpdatingLocation()
Air.shared.connectionCallbacks.append { cool in Air.shared.connectionCallbacks.append { cool in
DispatchQueue.main.async { DispatchQueue.main.async {
@ -148,10 +152,12 @@ struct EmulationView: View {
print(cool) print(cool)
startgame = nil startgame = nil
stop_emulation() stop_emulation()
try? Ryujinx.shared.stop() try? ryujijnx.stop()
} }
} }
} }
.onKeyPress()
.focused($isFocused)
.onChange(of: scenePhase) { newPhase in .onChange(of: scenePhase) { newPhase in
// Detect when the app enters the background // Detect when the app enters the background
if newPhase == .background { if newPhase == .background {
@ -172,4 +178,32 @@ struct EmulationView: View {
// } // }
} }
} }
func nextAspectRatio(current: AspectRatio) -> AspectRatio {
let all = AspectRatio.allCases
if let index = all.firstIndex(of: current) {
let nextIndex = (index + 1) % all.count
return all[nextIndex]
} else {
return .fixed16x9 // Default fallback
}
}
} }
// This is just to stop the sound on macOS when doing a keypress
extension View {
func onKeyPress() -> some View {
if #available(iOS 17.0, *), ProcessInfo.processInfo.isiOSAppOnMac {
return AnyView(self
.focusable()
.focusEffectDisabled()
.onKeyPress { _ in
return .handled
})
} else {
return AnyView(self)
}
}
}

View File

@ -14,12 +14,21 @@ struct PerformanceOverlayView: View {
var body: some View { var body: some View {
VStack { VStack {
Text("\(fpsmonitor.formatFPS())") if ProcessInfo.processInfo.isLowPowerModeEnabled {
.foregroundStyle(.white) Text("\(fpsmonitor.formatFPS())")
.stroke(color: .black, width: 2) .foregroundStyle(.white)
Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage)) .stroke(color: .orange, width: 2)
.foregroundStyle(.white) Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage))
.stroke(color: .black, width: 2) .foregroundStyle(.white)
.stroke(color: .orange, width: 2)
} else {
Text("\(fpsmonitor.formatFPS())")
.foregroundStyle(.white)
.stroke(color: .black, width: 2)
Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage))
.foregroundStyle(.white)
.stroke(color: .black, width: 2)
}
} }
} }
} }

View File

@ -0,0 +1,158 @@
//
// MetalViewContainer.swift
// MeloNX
//
// Created by Stossy11 on 06/07/2025.
//
import SwiftUI
struct MetalViewContainer: View {
@ObservedObject var ryujinx = Ryujinx.shared
@AppStorage("OldView") var oldView = true
@Environment(\.colorScheme) var colorScheme
@Environment(\.horizontalSizeClass) var horizontalSizeClass
var body: some View {
GeometryReader { geo in
ZStack {
if shouldStretchToFillScreen {
stretchedView
} else if oldView {
Color.black.edgesIgnoringSafeArea(.all)
oldStyleView(containerSize: geo.size)
} else {
modernView(containerSize: geo.size)
}
}
.animation(.easeInOut(duration: 0.3), value: ryujinx.aspectRatio)
.animation(.easeInOut(duration: 0.3), value: oldView)
}
}
// MARK: - View Components
private var stretchedView: some View {
MetalView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
}
private func oldStyleView(containerSize: CGSize) -> some View {
let size = targetSize(for: containerSize)
let isPortrait = containerSize.width < containerSize.height
return ZStack {
MetalView()
.frame(width: size.width, height: size.height)
.aspectRatio(contentMode: .fit)
.ignoresSafeArea(.container, edges: isPortrait ? .horizontal : .vertical)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private func modernView(containerSize: CGSize) -> some View {
let size = targetSize(for: containerSize)
let isPortrait = containerSize.width < containerSize.height
let isPhone = UIDevice.current.userInterfaceIdiom == .phone
let scale = calculateScale(isPortrait: isPortrait, isPhone: isPhone)
return ZStack {
borderedMetalView(size: size)
.scaleEffect(scale)
.ignoresSafeArea(.container, edges: isPortrait ? .horizontal : .vertical)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private func borderedMetalView(size: CGSize) -> some View {
let cornerRadius: CGFloat = 16
let borderWidth: CGFloat = 2
return ZStack {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.fill(backgroundColor)
.shadow(color: shadowColor, radius: 8, x: 0, y: 2)
MetalView()
.frame(width: size.width - borderWidth * 2, height: size.height - borderWidth * 2)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius - borderWidth, style: .continuous))
}
.overlay(
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
.strokeBorder(borderColor, lineWidth: borderWidth)
)
.frame(width: size.width, height: size.height)
}
// MARK: - Computed Properties
private var shouldStretchToFillScreen: Bool {
ryujinx.aspectRatio == .stretched ||
(ryujinx.aspectRatio == .fixed4x3 && isScreenAspectRatio(4, 3))
}
private var borderColor: Color {
colorScheme == .light ? Color.gray : Color(UIColor.darkGray)
}
private var backgroundColor: Color {
colorScheme == .light ? .white : Color(.systemGray6)
}
private var shadowColor: Color {
colorScheme == .light ? .black.opacity(0.1) : .black.opacity(0.3)
}
// MARK: - Helper Methods
private func calculateScale(isPortrait: Bool, isPhone: Bool) -> CGFloat {
let baseScale: CGFloat = isPhone ? 0.95 : 1.0
return isPortrait ? baseScale : baseScale * 0.92
}
private func targetSize(for containerSize: CGSize) -> CGSize {
let targetAspect: CGFloat = {
switch ryujinx.aspectRatio {
case .fixed4x3: return 4.0 / 3.0
case .fixed16x9: return 16.0 / 9.0
case .fixed16x10: return 16.0 / 10.0
case .fixed21x9: return 21.0 / 9.0
case .fixed32x9: return 32.0 / 10.0
case .stretched: return containerSize.width / containerSize.height
}
}()
let safeArea = UIApplication.shared.windows.first?.safeAreaInsets ?? .zero
let adjustedContainer = CGSize(
width: containerSize.width - safeArea.left - safeArea.right,
height: containerSize.height - safeArea.top - safeArea.bottom
)
if ryujinx.aspectRatio == .stretched {
return adjustedContainer
}
let containerAspect = adjustedContainer.width / adjustedContainer.height
if containerAspect > targetAspect {
let height = adjustedContainer.height
let width = height * targetAspect
return CGSize(width: width, height: height)
} else {
let width = adjustedContainer.width
let height = width / targetAspect
return CGSize(width: width, height: height)
}
}
private func isScreenAspectRatio(_ targetWidth: CGFloat, _ targetHeight: CGFloat, tolerance: CGFloat = 0.05) -> Bool {
let screenSize = UIScreen.main.bounds.size
let actualRatio = screenSize.width / screenSize.height
let targetRatio = targetWidth / targetHeight
return abs(actualRatio - targetRatio) < tolerance
}
}

View File

@ -99,7 +99,7 @@ class MeloMTKView: MTKView {
let disabled = UserDefaults.standard.bool(forKey: "disableTouch") let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
guard !disabled else { return } guard !disabled else { return }
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9) setAspectRatio(Ryujinx.shared.aspectRatio)
for touch in touches { for touch in touches {
let location = touch.location(in: self) let location = touch.location(in: self)
@ -153,7 +153,7 @@ class MeloMTKView: MTKView {
let disabled = UserDefaults.standard.bool(forKey: "disableTouch") let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
guard !disabled else { return } guard !disabled else { return }
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9) setAspectRatio(Ryujinx.shared.aspectRatio)
for touch in touches { for touch in touches {
if ignoredTouches.contains(touch) { if ignoredTouches.contains(touch) {

View File

@ -21,14 +21,13 @@ struct MetalView: UIViewRepresentable {
fatalError("[Swift] Error: MTKView's layer is not a CAMetalLayer") fatalError("[Swift] Error: MTKView's layer is not a CAMetalLayer")
} }
metalLayer.device = MTLCreateSystemDefaultDevice() notnil(metalLayer.device) ? () : (metalLayer.device = MTLCreateSystemDefaultDevice())
let layerPtr = Unmanaged.passUnretained(metalLayer).toOpaque() let layerPtr = Unmanaged.passUnretained(metalLayer).toOpaque()
set_native_window(layerPtr) set_native_window(layerPtr)
Ryujinx.shared.emulationUIView = view Ryujinx.shared.emulationUIView = view
Ryujinx.shared.metalLayer = metalLayer Ryujinx.shared.metalLayer = metalLayer
return view return view
@ -51,5 +50,7 @@ struct MetalView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: Context) { func updateUIView(_ uiView: UIView, context: Context) {
// nothin // nothin
print(context)
} }
} }

View File

@ -10,8 +10,7 @@ import MetalKit
struct TouchView: UIViewRepresentable { struct TouchView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView { func makeUIView(context: Context) -> UIView {
let view = MeloMTKView() return MeloMTKView()
return view
} }
func updateUIView(_ uiView: UIView, context: Context) {} func updateUIView(_ uiView: UIView, context: Context) {}

View File

@ -52,6 +52,9 @@ struct ContentView: View {
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true @AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false @AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false @AppStorage("ignoreJIT") var ignoreJIT: Bool = false
@AppStorage("DUAL_MAPPED_JIT") var dualMapped: Bool = false
@AppStorage("DUAL_MAPPED_JIT_edit") var dualMappededit: Bool = false
// Loading Animation // Loading Animation
@AppStorage("showlogsloading") var showlogsloading: Bool = true @AppStorage("showlogsloading") var showlogsloading: Bool = true
@ -79,6 +82,12 @@ struct ContentView: View {
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"), MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
] ]
if #available(iOS 19, *) {
setenv("HAS_TXM", ProcessInfo.processInfo.hasTXM ? "1" : "0", 1)
} else {
setenv("HAS_TXM", "0", 1)
}
_settings = State(initialValue: defaultSettings) _settings = State(initialValue: defaultSettings)
initializeSDL() initializeSDL()
@ -146,9 +155,7 @@ struct ContentView: View {
let _ = loadSettings() let _ = loadSettings()
isLoading = true isLoading = true
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in refreshControllersList()
refreshControllersList()
}
UserDefaults.standard.set(false, forKey: "lockInApp") UserDefaults.standard.set(false, forKey: "lockInApp")
@ -322,7 +329,9 @@ struct ContentView: View {
controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") } controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") }
if controllersList.count == 1 { if controllersList.count == 1 {
currentControllers.append(controllersList[0]) if !ProcessInfo.processInfo.isiOSAppOnMac {
currentControllers.append(controllersList[0])
}
} else if (controllersList.count - 1) >= 1 { } else if (controllersList.count - 1) >= 1 {
for controller in controllersList { for controller in controllersList {
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) { if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
@ -392,6 +401,12 @@ struct ContentView: View {
if syncqsubmits { if syncqsubmits {
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "1", 1) setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "1", 1)
} }
if dualMapped {
setenv("DUAL_MAPPED_JIT", "1", 1)
} else {
setenv("DUAL_MAPPED_JIT", "0", 1)
}
} }
private func setMoltenVKSettings() { private func setMoltenVKSettings() {
@ -405,7 +420,11 @@ struct ContentView: View {
if jitStreamerEB { if jitStreamerEB {
jitStreamerEB = false // byee jitstreamer eb jitStreamerEB = false // byee jitstreamer eb
} }
print("Has TXM? \(ProcessInfo.processInfo.hasTXM)")
if #available(iOS 19, *), !dualMappededit {
dualMapped = !ProcessInfo.processInfo.isiOSAppOnMac
dualMappededit = true
}
if !ryujinx.jitenabled { if !ryujinx.jitenabled {
if useTrollStore { if useTrollStore {
@ -415,12 +434,7 @@ struct ContentView: View {
} else if jitStreamerEB { } else if jitStreamerEB {
enableJITEB() enableJITEB()
} else { } else {
if !allocateTest(), checkDebugged() { // nothing
loop_heartbeat()
sleep(5)
let cool = String(cString: attach(getpid())!)
print(cool)
}
} }
} }
} }
@ -429,8 +443,6 @@ struct ContentView: View {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true), if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
components.host == "game" { components.host == "game" {
refreshControllersList()
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value { if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
game = ryujinx.games.first(where: { $0.titleId == text }) game = ryujinx.games.first(where: { $0.titleId == text })
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value { } else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {

View File

@ -27,13 +27,21 @@ struct GameLibraryView: View {
@State var isSelectingGameFile = false @State var isSelectingGameFile = false
@State var isViewingGameInfo: Bool = false @State var isViewingGameInfo: Bool = false
@State var gamePerGameSettings: Game? @State var gamePerGameSettings: Game?
@State var gameController: Game?
var isShowingPerGameSettings: Binding<Bool> { var isShowingPerGameSettings: Binding<Bool> {
Binding<Bool> { Binding<Bool> {
gamePerGameSettings != nil gamePerGameSettings != nil
} set: { value in } set: { value in
!value ? gamePerGameSettings = nil : () !value ? gamePerGameSettings = nil : ()
} }
}
var isShowingGameController: Binding<Bool> {
Binding<Bool> {
gameController != nil
} set: { value in
!value ? gameController = nil : ()
}
} }
@State var isSelectingGameUpdate: Bool = false @State var isSelectingGameUpdate: Bool = false
@State var isSelectingGameDLC: Bool = false @State var isSelectingGameDLC: Bool = false
@ -213,6 +221,9 @@ struct GameLibraryView: View {
.sheet(isPresented: isShowingPerGameSettings) { .sheet(isPresented: isShowingPerGameSettings) {
PerGameSettingsView(titleId: gamePerGameSettings!.titleId) PerGameSettingsView(titleId: gamePerGameSettings!.titleId)
} }
.fullScreenCover(isPresented: isShowingGameController) {
ControllerView(isEditing: isShowingGameController, gameId: gameController?.titleId)
}
.sheet(isPresented: Binding( .sheet(isPresented: Binding(
get: { isViewingGameInfo && gameInfo != nil }, get: { isViewingGameInfo && gameInfo != nil },
set: { newValue in set: { newValue in
@ -284,6 +295,7 @@ struct GameLibraryView: View {
isSelectingGameDLC: $isSelectingGameDLC, isSelectingGameDLC: $isSelectingGameDLC,
gameRequirements: $gameRequirements, gameRequirements: $gameRequirements,
gameInfo: $gameInfo, gameInfo: $gameInfo,
isShowingGameController: $gameController,
perGameSettings: $gamePerGameSettings perGameSettings: $gamePerGameSettings
) )
.padding(.horizontal) .padding(.horizontal)
@ -302,6 +314,7 @@ struct GameLibraryView: View {
isSelectingGameDLC: $isSelectingGameDLC, isSelectingGameDLC: $isSelectingGameDLC,
gameRequirements: $gameRequirements, gameRequirements: $gameRequirements,
gameInfo: $gameInfo, gameInfo: $gameInfo,
isShowingGameController: $gameController,
perGameSettings: $gamePerGameSettings perGameSettings: $gamePerGameSettings
) )
.padding(.horizontal) .padding(.horizontal)
@ -502,6 +515,12 @@ struct GameLibraryView: View {
} label: { } label: {
Label("\(game.titleName) Settings", systemImage: "gear") Label("\(game.titleName) Settings", systemImage: "gear")
} }
Button {
gameController = game
} label: {
Label("Controller Layout", systemImage: "formfitting.gamecontroller")
}
} }
Section { Section {
@ -527,6 +546,12 @@ struct GameLibraryView: View {
Label("Remove from Recents", systemImage: "trash") Label("Remove from Recents", systemImage: "trash")
} }
Button(role: .destructive) {
Ryujinx.clearShaderCache(game.titleId)
} label: {
Label("Clear Shader Cache", systemImage: "trash")
}
if #available(iOS 15, *) { if #available(iOS 15, *) {
Button(role: .destructive) { Button(role: .destructive) {
deleteGame(game: game) deleteGame(game: game)
@ -797,6 +822,7 @@ struct GameListRow: View {
@Binding var isSelectingGameDLC: Bool @Binding var isSelectingGameDLC: Bool
@Binding var gameRequirements: [GameRequirements] @Binding var gameRequirements: [GameRequirements]
@Binding var gameInfo: Game? @Binding var gameInfo: Game?
@Binding var isShowingGameController: Game?
@StateObject private var settingsManager = PerGameSettingsManager.shared @StateObject private var settingsManager = PerGameSettingsManager.shared
@Binding var perGameSettings: Game? @Binding var perGameSettings: Game?
@State var gametoDelete: Game? @State var gametoDelete: Game?
@ -939,6 +965,14 @@ struct GameListRow: View {
} label: { } label: {
Label("\(game.titleName) Settings", systemImage: "gear") Label("\(game.titleName) Settings", systemImage: "gear")
} }
// isShowingGameController
Button {
isShowingGameController = game
} label: {
Label("Controller Layout", systemImage: "formfitting.gamecontroller")
}
} }
Section { Section {
@ -958,6 +992,12 @@ struct GameListRow: View {
} }
Section { Section {
Button(role: .destructive) {
Ryujinx.clearShaderCache(game.titleId)
} label: {
Label("Clear Shader Cache", systemImage: "trash")
}
Button(role: .destructive) { Button(role: .destructive) {
gametoDelete = game gametoDelete = game
showGameDeleteConfirmation.toggle() showGameDeleteConfirmation.toggle()
@ -1114,6 +1154,12 @@ struct GameListRow: View {
} label: { } label: {
Label("Game Info", systemImage: "info.circle") Label("Game Info", systemImage: "info.circle")
} }
Button {
isShowingGameController = game
} label: {
Label("Controller Layout", systemImage: "formfitting.gamecontroller")
}
} }
Section { Section {
@ -1133,6 +1179,12 @@ struct GameListRow: View {
} }
Section { Section {
Button(role: .destructive) {
Ryujinx.clearShaderCache(game.titleId)
} label: {
Label("Clear Shader Cache", systemImage: "trash")
}
Button { Button {
gametoDelete = game gametoDelete = game
showGameDeleteConfirmation.toggle() showGameDeleteConfirmation.toggle()
@ -1238,30 +1290,10 @@ func pullGameCompatibility(completion: @escaping (Result<[GameRequirements], Err
extension View { extension View {
func wow(_ colorScheme: ColorScheme) -> some View { func wow(_ colorScheme: ColorScheme) -> some View {
if #available(iOS 26.0, *) { self
return self .background(
.glassEffect(Glass.regular, in: RoundedRectangle(cornerRadius: 12)
RoundedRectangle(cornerRadius: 12) .fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5))
) )
} else {
return self
.background(
RoundedRectangle(cornerRadius: 12)
.fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5))
)
}
} }
} }
extension View {
@available(iOS, introduced: 14.0, deprecated: 19.0, message: "")
func glassEffect(_ style: Glass, in shape: some Shape) -> some View {
return self
}
}
@available(iOS, introduced: 14.0, deprecated: 19.0, message: "")
struct Glass: Hashable {
static var regular = Glass()
}

View File

@ -232,6 +232,7 @@ struct SettingsViewNew: View {
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = false @AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = false
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false @AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
@AppStorage("DUAL_MAPPED_JIT") var dualMapped: Bool = false
@AppStorage("performacehud") var performacehud: Bool = false @AppStorage("performacehud") var performacehud: Bool = false
@ -260,20 +261,18 @@ struct SettingsViewNew: View {
@AppStorage("disableTouch") var disableTouch = false @AppStorage("disableTouch") var disableTouch = false
@AppStorage("disableTouch") var blackScreen = false
@AppStorage("location-enabled") var locationenabled: Bool = false @AppStorage("location-enabled") var locationenabled: Bool = false
@AppStorage("runOnMainThread") var runOnMainThread = false @AppStorage("runOnMainThread") var runOnMainThread = false
@AppStorage("oldSettingsUI") var oldSettingsUI = false @AppStorage("oldSettingsUI") var oldSettingsUI = false
@AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState()
let totalMemory = ProcessInfo.processInfo.physicalMemory let totalMemory = ProcessInfo.processInfo.physicalMemory
@AppStorage("lockInApp") var restartApp = false @AppStorage("lockInApp") var restartApp = false
@AppStorage("OldView") var oldView = true
@State private var showResolutionInfo = false @State private var showResolutionInfo = false
@State private var showAnisotropicInfo = false @State private var showAnisotropicInfo = false
@State private var showControllerInfo = false @State private var showControllerInfo = false
@ -375,12 +374,26 @@ struct SettingsViewNew: View {
color: .blue color: .blue
) )
InfoCard( let versionPart = ProcessInfo.processInfo.operatingSystemVersionString.replacingOccurrences(of: "Version ", with: "")
title: "System",
value: "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)", let parts = versionPart.components(separatedBy: " (Build ")
icon: "applelogo", if parts.count == 2 {
color: .gray let version = parts[0]
) let build = parts[1].replacingOccurrences(of: ")", with: "")
InfoCard(
title: "System",
value: "\(ProcessInfo.processInfo.isiOSAppOnMac ? "macOS" : UIDevice.current.systemName) \(version) (\(build))",
icon: "applelogo",
color: .gray
)
} else {
InfoCard(
title: "System",
value: "\(ProcessInfo.processInfo.isiOSAppOnMac ? "macOS" : UIDevice.current.systemName) \(UIDevice.current.systemVersion)",
icon: "applelogo",
color: .gray
)
}
InfoCard( InfoCard(
title: "Increased Memory Limit", title: "Increased Memory Limit",
@ -551,6 +564,16 @@ struct SettingsViewNew: View {
.font(.subheadline) .font(.subheadline)
.foregroundColor(.secondary) .foregroundColor(.secondary)
if ProcessInfo.processInfo.isiOSAppOnMac {
Text("macOS \(ProcessInfo.processInfo.operatingSystemVersionString)")
.font(.subheadline.weight(.medium))
.foregroundColor(.secondary)
Text("·")
.font(.subheadline)
.foregroundColor(.secondary)
}
Text("Version \(appVersion)") Text("Version \(appVersion)")
.font(.subheadline) .font(.subheadline)
.foregroundColor(.secondary) .foregroundColor(.secondary)
@ -755,6 +778,11 @@ struct SettingsViewNew: View {
// Aspect ratio card // Aspect ratio card
SettingsCard { SettingsCard {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
SettingsToggle(isOn: $oldView, icon: "rectangle.on.rectangle.dashed", label: "Old Display UI")
Divider()
labelWithIcon("Aspect Ratio", iconName: "rectangle.expand.vertical") labelWithIcon("Aspect Ratio", iconName: "rectangle.expand.vertical")
.font(.headline) .font(.headline)
@ -825,16 +853,6 @@ struct SettingsViewNew: View {
Divider() Divider()
SettingsToggle(isOn: $swapBandA, icon: "rectangle.2.swap", label: "Swap Face Buttons (Physical Controller)") SettingsToggle(isOn: $swapBandA, icon: "rectangle.2.swap", label: "Swap Face Buttons (Physical Controller)")
Divider()
DisclosureGroup("Toggle Buttons") {
SettingsToggle(isOn: $toggleButtons.toggle1, icon: "circle.grid.cross.right.filled", label: "Toggle A")
SettingsToggle(isOn: $toggleButtons.toggle2, icon: "circle.grid.cross.down.filled", label: "Toggle B")
SettingsToggle(isOn: $toggleButtons.toggle3, icon: "circle.grid.cross.up.filled", label: "Toggle X")
SettingsToggle(isOn: $toggleButtons.toggle4, icon: "circle.grid.cross.left.filled", label: "Toggle Y")
}
.padding(.vertical, 6)
} }
} }
@ -1242,6 +1260,7 @@ struct SettingsViewNew: View {
SettingsSection(title: "Miscellaneous Options") { SettingsSection(title: "Miscellaneous Options") {
SettingsCard { SettingsCard {
VStack(spacing: 4) { VStack(spacing: 4) {
if UIDevice.current.userInterfaceIdiom == .pad { if UIDevice.current.userInterfaceIdiom == .pad {
SettingsToggle(isOn: $toggleGreen, icon: "arrow.clockwise", label: "Toggle Color Green when \"ON\"") SettingsToggle(isOn: $toggleGreen, icon: "arrow.clockwise", label: "Toggle Color Green when \"ON\"")
@ -1254,11 +1273,6 @@ struct SettingsViewNew: View {
Divider() Divider()
if colorScheme == .light {
SettingsToggle(isOn: $blackScreen, icon: "iphone.slash", label: "Black Screen when using AirPlay")
Divider()
}
Button { Button {
showAppIconSwitcher = true showAppIconSwitcher = true
@ -1354,20 +1368,25 @@ struct SettingsViewNew: View {
Divider() Divider()
SettingsToggle(isOn: $dualMapped, icon: "light.strip.2", label: "Dual Mapped JIT")
Divider()
SettingsToggle(isOn: $checkForUpdate, icon: "square.and.arrow.down", label: "Check for Updates") SettingsToggle(isOn: $checkForUpdate, icon: "square.and.arrow.down", label: "Check for Updates")
if ryujinx.firmwareversion != "0" { Divider()
Divider()
Button { Button {
Ryujinx.shared.removeFirmware() Ryujinx.clearShaderCache()
} label: { } label: {
HStack { HStack {
Text("Remove Firmware") Image(systemName: "trash")
.foregroundColor(.blue) .foregroundColor(.blue)
Spacer() Text("Clear All Shader Cache")
} .foregroundColor(.primary)
.padding(.vertical, 8) Spacer()
} }
.padding(.vertical, 8)
} }
} }
} }

View File

@ -41,7 +41,11 @@ struct MeloNXApp: App {
@AppStorage("autoJIT") var autoJIT = false @AppStorage("autoJIT") var autoJIT = false
@State var fourgbiPad = false @State var fourgbiPad = false
@State var ios19 = false
@AppStorage("4GB iPad") var ignores = false @AppStorage("4GB iPad") var ignores = false
@AppStorage("iOS19") var ignores19 = false
@AppStorage("DUAL_MAPPED_JIT") var dualMapped: Bool = false
@AppStorage("DUAL_MAPPED_JIT_edit") var dualMappededit: Bool = false
// String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000) // String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000)
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
@ -77,10 +81,19 @@ struct MeloNXApp: App {
withAnimation(.easeOut) { withAnimation(.easeOut) {
finishedStorage = newValue finishedStorage = newValue
} }
if #available(iOS 19, *), newValue {
dualMapped = !ProcessInfo.processInfo.isiOSAppOnMac
dualMappededit = true
}
} }
} }
} }
.onAppear() { .onAppear() {
if #available(iOS 19, *), ProcessInfo.processInfo.hasTXM, !ignores19 {
ios19 = true
}
if UIDevice.current.userInterfaceIdiom == .pad && !ignores { if UIDevice.current.userInterfaceIdiom == .pad && !ignores {
print((Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000)) print((Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000))
if round(Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000) <= 4 { if round(Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000) <= 4 {
@ -148,3 +161,4 @@ func changeAppUI(_ string: String) -> String? {
guard let data = Data(base64Encoded: string) else { return nil } guard let data = Data(base64Encoded: string) else { return nil }
return String(data: data, encoding: .utf8) return String(data: data, encoding: .utf8)
} }

View File

@ -0,0 +1,615 @@
{
"sourceLanguage" : "en",
"strings" : {
"" : {
},
"-" : {
"comment" : "A button that decreases the size of a button.",
"isCommentAutoGenerated" : true
},
"·" : {
"comment" : "A separator displayed between two lines of text.",
"isCommentAutoGenerated" : true
},
"**%@** | %@" : {
"comment" : "A heading displaying the name of a game, followed by its title ID.",
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "**%1$@** | %2$@"
}
}
}
},
"**File Type**" : {
"comment" : "A label displayed above the file type of a game.",
"isCommentAutoGenerated" : true
},
"**Game Size**" : {
"comment" : "A label displayed above the size of a game file.",
"isCommentAutoGenerated" : true
},
"**Game URL**" : {
"comment" : "A label displayed above the URL of a game file.",
"isCommentAutoGenerated" : true
},
"**Title ID**" : {
"comment" : "A button that copies the title ID to the clipboard.",
"isCommentAutoGenerated" : true
},
"**Version**" : {
"comment" : "A label displayed before the version number of a game.",
"isCommentAutoGenerated" : true
},
"%.1fx" : {
"comment" : "A placeholder text for the slider value.",
"isCommentAutoGenerated" : true
},
"%.2fx" : {
"comment" : "The current resolution scale value is displayed in blue text.",
"isCommentAutoGenerated" : true
},
"%@" : {
"comment" : "A label displaying the current FPS and memory usage in low power mode.",
"isCommentAutoGenerated" : true
},
"%@ DLCs" : {
},
"%@ RAM" : {
"comment" : "A subheading displaying the amount of RAM available on a device.",
"isCommentAutoGenerated" : true
},
"%@ Settings" : {
"comment" : "A button that allows the user to access the settings for a specific game.",
"isCommentAutoGenerated" : true
},
"%@ Updates" : {
},
"%lld icons" : {
"comment" : "A label displaying the number of icons created by a developer.",
"isCommentAutoGenerated" : true
},
"%llu bytes" : {
"comment" : "A label displaying the size of a file.",
"isCommentAutoGenerated" : true
},
"•" : {
"comment" : "A separator between the developer name and the version number.",
"isCommentAutoGenerated" : true
},
"+" : {
"comment" : "A button that increases the scale of a button when pressed.",
"isCommentAutoGenerated" : true
},
"0.1x" : {
"comment" : "The word \"low\" is used here to mean \"minimum\".",
"isCommentAutoGenerated" : true
},
"0GB" : {
"comment" : "A placeholder text displayed when the system requirements of a game cannot be determined.",
"isCommentAutoGenerated" : true
},
"3.0x" : {
"comment" : "The \"1x\" text is a placeholder.",
"isCommentAutoGenerated" : true
},
"16x" : {
"comment" : "The \"16x\" text in the slider.",
"isCommentAutoGenerated" : true
},
"About" : {
},
"Add Controller" : {
"comment" : "A button that allows adding a controller to the list.",
"isCommentAutoGenerated" : true
},
"Add DLC" : {
"comment" : "A button that adds game DLCs.",
"isCommentAutoGenerated" : true
},
"Add Game" : {
"comment" : "A button that adds a game to the library.",
"isCommentAutoGenerated" : true
},
"Add ROM files to get started with your gaming experience" : {
"comment" : "A call-to-action displayed below the message that no games were found.",
"isCommentAutoGenerated" : true
},
"Add Update" : {
"comment" : "A button that adds a new game update.",
"isCommentAutoGenerated" : true
},
"Additional Arguments" : {
"comment" : "A heading displayed above a text field used to enter additional arguments for the virtual machine.",
"isCommentAutoGenerated" : true
},
"Adjust the internal Anisotropic filtering. Higher values improve texture quality at angles but may reduce performance. Default at 0 lets game decide." : {
"comment" : "A description displayed in an alert when the user taps the \"Max Anisotropic Filtering\" setting.",
"isCommentAutoGenerated" : true
},
"Adjust the internal rendering resolution. Higher values improve visuals but may reduce performance." : {
"comment" : "A description displayed in an alert when the user taps the \"Info\" icon next to \"Resolution Scale\".",
"isCommentAutoGenerated" : true
},
"Adjust the On-Screen Controller size." : {
"comment" : "A description displayed when the user taps the \"Info\" icon next to the \"Scale\" label in the \"On-Screen Controller\" card.",
"isCommentAutoGenerated" : true
},
"Adjust the On-Screen Controller transparency." : {
"comment" : "A description displayed in an alert dialog box.",
"isCommentAutoGenerated" : true
},
"Always show Joystick Background" : {
"comment" : "A label displayed above a toggle button that controls whether the joystick background is always shown.",
"isCommentAutoGenerated" : true
},
"App Icon Switcher" : {
"comment" : "A",
"isCommentAutoGenerated" : true
},
"Applets" : {
"comment" : "A menu that allows users to launch specific apps on their Switch.",
"isCommentAutoGenerated" : true
},
"Are you sure you want to delete %@?" : {
},
"Are you sure you want to delete this game?" : {
},
"Available Layouts" : {
},
"Button Scale: %@" : {
"comment" : "A label displaying the current scale of a button.",
"isCommentAutoGenerated" : true
},
"Cancel" : {
"comment" : "The title of the alert that confirms deleting a custom layout.",
"isCommentAutoGenerated" : true
},
"Changelog:" : {
"comment" : "A heading displayed above a list of changes in the latest update.",
"isCommentAutoGenerated" : true
},
"Choose App Icon" : {
"comment" : "The title displayed in the navigation bar above the list of app icons.",
"isCommentAutoGenerated" : true
},
"Clear All Shader Cache" : {
"comment" : "A",
"isCommentAutoGenerated" : true
},
"Clear Shader Cache" : {
"comment" : "A button that deletes the shader cache for a game.",
"isCommentAutoGenerated" : true
},
"Close" : {
"comment" : "The label for the button to close the update sheet.",
"isCommentAutoGenerated" : true
},
"Continue" : {
"comment" : "A button that dismisses the alert.",
"isCommentAutoGenerated" : true
},
"Controller Configuration" : {
"comment" : "A heading displayed above the controller configuration options.",
"isCommentAutoGenerated" : true
},
"Controller Layout" : {
},
"Controller Selection" : {
"comment" : "A heading displayed above a list of available controllers.",
"isCommentAutoGenerated" : true
},
"Copy Layout" : {
"comment" : "A title displayed in the navigation bar of the view.",
"isCommentAutoGenerated" : true
},
"Copy Layout From..." : {
"comment" : "A button label to copy a layout from another game.",
"isCommentAutoGenerated" : true
},
"Copy Title ID" : {
"comment" : "A button label to copy a text to the clipboard.",
"isCommentAutoGenerated" : true
},
"CPU Configuration" : {
"comment" : "A heading displayed above the CPU configuration options.",
"isCommentAutoGenerated" : true
},
"Current Game" : {
"comment" : "A heading displayed above the information about the current game.",
"isCommentAutoGenerated" : true
},
"Custom Layout" : {
"comment" : "A caption displayed next to a checkmark icon.",
"isCommentAutoGenerated" : true
},
"Default Layout" : {
"comment" : "A button label for copying the default layout.",
"isCommentAutoGenerated" : true
},
"Delete" : {
"comment" : "The destructive button in an alert that deletes a custom layout.",
"isCommentAutoGenerated" : true
},
"Delete Custom Layout" : {
"comment" : "A button label to delete a custom layout.",
"isCommentAutoGenerated" : true
},
"Delete Game" : {
"comment" : "A destructive button that deletes a game from the library.",
"isCommentAutoGenerated" : true
},
"Dismiss" : {
"comment" : "A button that dismisses the game info sheet.",
"isCommentAutoGenerated" : true
},
"DLC Manager" : {
"comment" : "A button that displays a sheet for managing downloadable content for a game.",
"isCommentAutoGenerated" : true
},
"Done" : {
"comment" : "A button that toggles between editing mode and non-editing mode.",
"isCommentAutoGenerated" : true
},
"Download Now" : {
"comment" : "The action button to download the app update.",
"isCommentAutoGenerated" : true
},
"Edit" : {
"comment" : "A button that toggles between editing mode and non-editing mode.",
"isCommentAutoGenerated" : true
},
"Exit (Unstable)" : {
"comment" : "A destructive action displayed in a menu.",
"isCommentAutoGenerated" : true
},
"Finish Setup" : {
},
"Game" : {
},
"Game Info" : {
"comment" : "A button that displays information about a game.",
"isCommentAutoGenerated" : true
},
"Game Library" : {
"comment" : "The title displayed in the navigation bar for the game library view.",
"isCommentAutoGenerated" : true
},
"Game: %@" : {
"comment" : "A label displaying the identifier of the game being played.",
"isCommentAutoGenerated" : true
},
"Games" : {
"comment" : "A tab item that displays a list of games.",
"isCommentAutoGenerated" : true
},
"Hide" : {
"comment" : "A button that hides the edit controls when pressed.",
"isCommentAutoGenerated" : true
},
"Hide ABXY / Arrow Buttons" : {
"comment" : "A label displayed above a toggle button that controls whether the joystick buttons are hidden or not.",
"isCommentAutoGenerated" : true
},
"Hide Button" : {
"comment" : "A label displayed above a toggle switch that controls whether a button is hidden or not.",
"isCommentAutoGenerated" : true
},
"Hide Joystick" : {
"comment" : "A button to hide the joystick.",
"isCommentAutoGenerated" : true
},
"Home Menu (Broken)" : {
"comment" : "A button that launches the currently Broken Home Menu applet."
},
"Info" : {
},
"Information" : {
"comment" : "A heading displayed above a list of information about a game.",
"isCommentAutoGenerated" : true
},
"Install Firmware" : {
"comment" : "A button that allows users to install the latest firmware for their Switch.",
"isCommentAutoGenerated" : true
},
"JIT (Just-In-Time) compilation allows MeloNX to run code at as fast as possible by translating it dynamically. This is necessary for running this emulator." : {
"comment" : "A description of what JIT is.",
"isCommentAutoGenerated" : true
},
"JIT Enabled" : {
"comment" : "A heading displayed above the status of JIT and the amount of RAM.",
"isCommentAutoGenerated" : true
},
"JIT Not Acquired" : {
"comment" : "A heading displayed above the status of JIT and the amount of RAM.",
"isCommentAutoGenerated" : true
},
"Joystick" : {
"comment" : "A label displayed inside the circle of a joystick.",
"isCommentAutoGenerated" : true
},
"Joystick Scale: %@" : {
"comment" : "A label displaying the current scale of a joystick.",
"isCommentAutoGenerated" : true
},
"Larger" : {
"comment" : "A placeholder text that indicates the minimum value of the slider.",
"isCommentAutoGenerated" : true
},
"Launch ${gameName}" : {
},
"Launch Game" : {
"comment" : "Title of the intent.",
"isCommentAutoGenerated" : true
},
"Launch Mii Maker" : {
"comment" : "A button that launches the Mii Maker applet.",
"isCommentAutoGenerated" : true
},
"Launches the Selected Game." : {
"comment" : "A short description of the intent.",
"isCommentAutoGenerated" : true
},
"Layout Actions" : {
"comment" : "A heading displayed above a list of actions related to layouts.",
"isCommentAutoGenerated" : true
},
"Layout Options" : {
"comment" : "A button that allows the user to customize the layout of the controller.",
"isCommentAutoGenerated" : true
},
"Less Transparent" : {
},
"Library" : {
"comment" : "A label displayed above the list of games in the library.",
"isCommentAutoGenerated" : true
},
"Loading %@" : {
"comment" : "A label displayed while the game is loading.",
"isCommentAutoGenerated" : true
},
"macOS %@" : {
"comment" : "A subheading displaying the macOS version running on the user's device.",
"isCommentAutoGenerated" : true
},
"Make Button Toggle" : {
"comment" : "A checkbox displayed next to a text label.",
"isCommentAutoGenerated" : true
},
"Max Anisotropic Filtering" : {
"comment" : "A title displayed above a description of a setting.",
"isCommentAutoGenerated" : true
},
"Memory Manager Mode" : {
"comment" : "A label displayed above a picker view that allows the user to choose the memory manager mode.",
"isCommentAutoGenerated" : true
},
"More Transparent" : {
"comment" : "A heading displayed above the opacity of a UI element.",
"isCommentAutoGenerated" : true
},
"No controllers selected (Keyboard will be used)" : {
"comment" : "A text indicating that no controllers are selected.",
"isCommentAutoGenerated" : true
},
"No DLCs Found" : {
"comment" : "A message displayed when no DLCs are found.",
"isCommentAutoGenerated" : true
},
"No Games Found" : {
"comment" : "A message displayed when a user has no games in their library.",
"isCommentAutoGenerated" : true
},
"No Updates Found" : {
"comment" : "The title of a view that displays a list of items.",
"isCommentAutoGenerated" : true
},
"Off" : {
"comment" : "A caption displayed above the slider for max anisotropic filtering.",
"isCommentAutoGenerated" : true
},
"OK" : {
"comment" : "The button label to dismiss the alert.",
"isCommentAutoGenerated" : true
},
"ON" : {
"comment" : "The text displayed when the toggle is on or off.",
"isCommentAutoGenerated" : true
},
"On-Screen Controller" : {
"comment" : "A heading displayed above the scale settings for the on-screen controller.",
"isCommentAutoGenerated" : true
},
"On-Screen Controller Opacity" : {
"comment" : "A heading displayed above the description of the opacity setting for the on-screen controller.",
"isCommentAutoGenerated" : true
},
"On-Screen Controller Scale" : {
"comment" : "A title displayed above a message about the scale of the on-screen controller.",
"isCommentAutoGenerated" : true
},
"Open Game" : {
"comment" : "A button that allows the user to open a game file.",
"isCommentAutoGenerated" : true
},
"Options" : {
"comment" : "A button that displays a menu with options for the game library.",
"isCommentAutoGenerated" : true
},
"Pause" : {
"comment" : "A label displayed above a button that pauses or plays the emulation.",
"isCommentAutoGenerated" : true
},
"Play" : {
"comment" : "A label displayed above a button that pauses or plays the emulation.",
"isCommentAutoGenerated" : true
},
"Play Now" : {
"comment" : "A button that allows the user to launch a game.",
"isCommentAutoGenerated" : true
},
"Player %lld: %@" : {
"comment" : "A label displaying the name of a controller and its position in the list of controllers.",
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Player %1$lld: %2$@"
}
}
}
},
"Recent Games" : {
"comment" : "A heading displayed above a list of recently played games.",
"isCommentAutoGenerated" : true
},
"Remove from Recents" : {
"comment" : "A button that deletes a game from the user's library.",
"isCommentAutoGenerated" : true
},
"Reset" : {
"comment" : "A button that resets all settings to their default values.",
"isCommentAutoGenerated" : true
},
"Reset Current" : {
"comment" : "A button that resets the layout to its default state.",
"isCommentAutoGenerated" : true
},
"Reset Selected" : {
"comment" : "A button that resets the layout settings for a specific button when pressed.",
"isCommentAutoGenerated" : true
},
"Reset to Default Layout" : {
"comment" : "A button label to reset a layout to its default state.",
"isCommentAutoGenerated" : true
},
"Resolution Scale" : {
"comment" : "The title of an alert displayed when the user taps the \"i\" icon next to the \"Resolution Scale\" label.",
"isCommentAutoGenerated" : true
},
"Save to Photos" : {
"comment" : "A button that saves an image to the user's Photos library.",
"isCommentAutoGenerated" : true
},
"Separate arguments with commas" : {
"comment" : "A text field that allows the user to specify additional arguments to pass to the game.",
"isCommentAutoGenerated" : true
},
"Set up your Nintendo Switch emulation environment by importing keys and firmware." : {
"comment" : "A description displayed below the welcome message.",
"isCommentAutoGenerated" : true
},
"Settings" : {
"comment" : "The title displayed in the navigation bar of the settings view.",
"isCommentAutoGenerated" : true
},
"Setup" : {
},
"Show MeloNX Folder" : {
"comment" : "A button that opens the folder containing the game files on macOS.",
"isCommentAutoGenerated" : true
},
"Show Setup Screen" : {
"comment" : "A button label that shows a setup screen.",
"isCommentAutoGenerated" : true
},
"Skip" : {
"comment" : "A button that allows the user to skip the setup process.",
"isCommentAutoGenerated" : true
},
"Skip Setup?" : {
"comment" : "A prompt displayed when the user wants to skip setup.",
"isCommentAutoGenerated" : true
},
"Smaller" : {
"comment" : "A label displayed above the slider that controls the size of the on-screen controller.",
"isCommentAutoGenerated" : true
},
"Tap the + button to add game DLCs." : {
"comment" : "A message displayed when no game DLCs are found.",
"isCommentAutoGenerated" : true
},
"Tap the + button to add game updates." : {
"comment" : "A description displayed when no game updates are found.",
"isCommentAutoGenerated" : true
},
"The cake is a lie" : {
"comment" : "A placeholder text for a card in the settings menu.",
"isCommentAutoGenerated" : true
},
"This will delete the custom layout for this game and revert to the default layout." : {
"comment" : "A message displayed when the user confirms to delete the custom layout.",
"isCommentAutoGenerated" : true
},
"Unsupported Device" : {
"comment" : "An alert that appears on iPad devices with less than 4 GB of memory.",
"isCommentAutoGenerated" : true
},
"Update Manager" : {
"comment" : "A button that displays a sheet for updating a game.",
"isCommentAutoGenerated" : true
},
"Using Default" : {
"comment" : "A caption displayed when the user has not customized a layout for a game.",
"isCommentAutoGenerated" : true
},
"v%@" : {
"comment" : "A text indicating the version of a game.",
"isCommentAutoGenerated" : true
},
"Version %@" : {
"comment" : "A label displaying the version of the app.",
"isCommentAutoGenerated" : true
},
"Version %@ Available!" : {
"comment" : "The title of the sheet that appears when a new version of the app is available.",
"isCommentAutoGenerated" : true
},
"Version %@ is available. You are currently on Version %@." : {
"comment" : "A message that informs users a new version of the app is available, along with the current version and the version that is available.",
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Version %1$@ is available. You are currently on Version %2$@."
}
}
}
},
"Waiting for JIT" : {
"comment" : "A heading displayed when MeloNX is waiting for JIT compilation to complete.",
"isCommentAutoGenerated" : true
},
"Welcome to MeloNX" : {
"comment" : "A welcome message displayed on the initial setup screen.",
"isCommentAutoGenerated" : true
},
"wow" : {
"comment" : "A placeholder text used for demonstration purposes.",
"isCommentAutoGenerated" : true
},
"Your Device is an iPad with %@ of memory, MeloNX has issues with those devices" : {
"comment" : "A message displayed when the user's iPad has less than 4 GB of memory.",
"isCommentAutoGenerated" : true
}
},
"version" : "1.1"
}

View File

@ -0,0 +1,28 @@
//
// BreakJIT.h
// BreakpointJIT
//
// Created by Stossy11 on 09/07/2025.
//
#ifndef BreakGetJITMapping_h
#define BreakGetJITMapping_h
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Function with inline assembly for JIT mapping
* @param bytes Size parameter for mapping
* @return char* pointer result
*/
__attribute__((noinline, optnone, naked)) char* BreakGetJITMapping(size_t bytes);
#ifdef __cplusplus
}
#endif
#endif /* BreakGetJITMapping_h */

View File

@ -1,330 +0,0 @@
#if 0
#elif defined(__arm64__) && __arm64__
// Generated by Apple Swift version 6.0.3 effective-5.10 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
#ifndef STOSJIT_SWIFT_H
#define STOSJIT_SWIFT_H
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgcc-compat"
#if !defined(__has_include)
# define __has_include(x) 0
#endif
#if !defined(__has_attribute)
# define __has_attribute(x) 0
#endif
#if !defined(__has_feature)
# define __has_feature(x) 0
#endif
#if !defined(__has_warning)
# define __has_warning(x) 0
#endif
#if __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif
#pragma clang diagnostic ignored "-Wauto-import"
#if defined(__OBJC__)
#include <Foundation/Foundation.h>
#endif
#if defined(__cplusplus)
#include <cstdint>
#include <cstddef>
#include <cstdbool>
#include <cstring>
#include <stdlib.h>
#include <new>
#include <type_traits>
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#endif
#if defined(__cplusplus)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module"
#if defined(__arm64e__) && __has_include(<ptrauth.h>)
# include <ptrauth.h>
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-macro-identifier"
# ifndef __ptrauth_swift_value_witness_function_pointer
# define __ptrauth_swift_value_witness_function_pointer(x)
# endif
# ifndef __ptrauth_swift_class_method_pointer
# define __ptrauth_swift_class_method_pointer(x)
# endif
#pragma clang diagnostic pop
#endif
#pragma clang diagnostic pop
#endif
#if !defined(SWIFT_TYPEDEFS)
# define SWIFT_TYPEDEFS 1
# if __has_include(<uchar.h>)
# include <uchar.h>
# elif !defined(__cplusplus)
typedef uint_least16_t char16_t;
typedef uint_least32_t char32_t;
# endif
typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
#endif
#if !defined(SWIFT_PASTE)
# define SWIFT_PASTE_HELPER(x, y) x##y
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
#endif
#if !defined(SWIFT_METATYPE)
# define SWIFT_METATYPE(X) Class
#endif
#if !defined(SWIFT_CLASS_PROPERTY)
# if __has_feature(objc_class_property)
# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
# else
# define SWIFT_CLASS_PROPERTY(...)
# endif
#endif
#if !defined(SWIFT_RUNTIME_NAME)
# if __has_attribute(objc_runtime_name)
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
# else
# define SWIFT_RUNTIME_NAME(X)
# endif
#endif
#if !defined(SWIFT_COMPILE_NAME)
# if __has_attribute(swift_name)
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
# else
# define SWIFT_COMPILE_NAME(X)
# endif
#endif
#if !defined(SWIFT_METHOD_FAMILY)
# if __has_attribute(objc_method_family)
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
# else
# define SWIFT_METHOD_FAMILY(X)
# endif
#endif
#if !defined(SWIFT_NOESCAPE)
# if __has_attribute(noescape)
# define SWIFT_NOESCAPE __attribute__((noescape))
# else
# define SWIFT_NOESCAPE
# endif
#endif
#if !defined(SWIFT_RELEASES_ARGUMENT)
# if __has_attribute(ns_consumed)
# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
# else
# define SWIFT_RELEASES_ARGUMENT
# endif
#endif
#if !defined(SWIFT_WARN_UNUSED_RESULT)
# if __has_attribute(warn_unused_result)
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
# else
# define SWIFT_WARN_UNUSED_RESULT
# endif
#endif
#if !defined(SWIFT_NORETURN)
# if __has_attribute(noreturn)
# define SWIFT_NORETURN __attribute__((noreturn))
# else
# define SWIFT_NORETURN
# endif
#endif
#if !defined(SWIFT_CLASS_EXTRA)
# define SWIFT_CLASS_EXTRA
#endif
#if !defined(SWIFT_PROTOCOL_EXTRA)
# define SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_ENUM_EXTRA)
# define SWIFT_ENUM_EXTRA
#endif
#if !defined(SWIFT_CLASS)
# if __has_attribute(objc_subclassing_restricted)
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# else
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# endif
#endif
#if !defined(SWIFT_RESILIENT_CLASS)
# if __has_attribute(objc_class_stub)
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub))
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME)
# else
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME)
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
# endif
#endif
#if !defined(SWIFT_PROTOCOL)
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_EXTENSION)
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
#endif
#if !defined(OBJC_DESIGNATED_INITIALIZER)
# if __has_attribute(objc_designated_initializer)
# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
# else
# define OBJC_DESIGNATED_INITIALIZER
# endif
#endif
#if !defined(SWIFT_ENUM_ATTR)
# if __has_attribute(enum_extensibility)
# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
# else
# define SWIFT_ENUM_ATTR(_extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM)
# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# if __has_feature(generalized_swift_name)
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# else
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
#endif
#if !defined(SWIFT_UNAVAILABLE)
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
#endif
#if !defined(SWIFT_UNAVAILABLE_MSG)
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
#endif
#if !defined(SWIFT_AVAILABILITY)
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
#endif
#if !defined(SWIFT_WEAK_IMPORT)
# define SWIFT_WEAK_IMPORT __attribute__((weak_import))
#endif
#if !defined(SWIFT_DEPRECATED)
# define SWIFT_DEPRECATED __attribute__((deprecated))
#endif
#if !defined(SWIFT_DEPRECATED_MSG)
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
#endif
#if !defined(SWIFT_DEPRECATED_OBJC)
# if __has_feature(attribute_diagnose_if_objc)
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
# else
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
# endif
#endif
#if defined(__OBJC__)
#if !defined(IBSegueAction)
# define IBSegueAction
#endif
#endif
#if !defined(SWIFT_EXTERN)
# if defined(__cplusplus)
# define SWIFT_EXTERN extern "C"
# else
# define SWIFT_EXTERN extern
# endif
#endif
#if !defined(SWIFT_CALL)
# define SWIFT_CALL __attribute__((swiftcall))
#endif
#if !defined(SWIFT_INDIRECT_RESULT)
# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result))
#endif
#if !defined(SWIFT_CONTEXT)
# define SWIFT_CONTEXT __attribute__((swift_context))
#endif
#if !defined(SWIFT_ERROR_RESULT)
# define SWIFT_ERROR_RESULT __attribute__((swift_error_result))
#endif
#if defined(__cplusplus)
# define SWIFT_NOEXCEPT noexcept
#else
# define SWIFT_NOEXCEPT
#endif
#if !defined(SWIFT_C_INLINE_THUNK)
# if __has_attribute(always_inline)
# if __has_attribute(nodebug)
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug))
# else
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline))
# endif
# else
# define SWIFT_C_INLINE_THUNK inline
# endif
#endif
#if defined(_WIN32)
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport)
#endif
#else
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL
#endif
#endif
#if defined(__OBJC__)
#if __has_feature(objc_modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
#endif
#endif
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="StosJIT",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif
#if defined(__OBJC__)
SWIFT_EXTERN char * _Nullable attach(int32_t pid) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
SWIFT_EXTERN char * _Nullable debugattachanddetachApp(char * _Nonnull bundleId) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
SWIFT_EXTERN void detach(void) SWIFT_NOEXCEPT;
SWIFT_EXTERN void loop_heartbeat(void) SWIFT_NOEXCEPT;
SWIFT_EXTERN BOOL writeZeroToMemory(uint64_t addr, int32_t length) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT;
#endif
#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#if defined(__cplusplus)
#endif
#pragma clang diagnostic pop
#endif
#else
#error unsupported Swift architecture
#endif

View File

@ -1,19 +0,0 @@
//
// StosJIT.h
// StosJIT
//
// Created by Stossy11 on 10/05/2025.
//
#import <Foundation/Foundation.h>
#import <StosJIT/idevice.h>
//! Project version number for StosJIT.
FOUNDATION_EXPORT double StosJITVersionNumber;
//! Project version string for StosJIT.
FOUNDATION_EXPORT const unsigned char StosJITVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <StosJIT/PublicHeader.h>

View File

@ -1,205 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>.DS_Store</key>
<data>
7Mfr8shT4pXWBr/plN+uNkIabdM=
</data>
<key>Headers/StosJIT-Swift.h</key>
<data>
h9vaTwhC6FlnyKmIkaxLQGlFd1g=
</data>
<key>Headers/StosJIT.h</key>
<data>
ggHr5wlLNIIPydwUL9Vxm6abxjo=
</data>
<key>Headers/idevice.h</key>
<data>
mHDz7368FsBID56/epJ2NgIkha4=
</data>
<key>Headers/plist.h</key>
<data>
bL/f0MQDpLfvIcI1zxPwMuJ/PfI=
</data>
<key>Info.plist</key>
<data>
ZTTwPKlta/gjXAr1HIHmyAxeU4E=
</data>
<key>Modules/StosJIT.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo</key>
<data>
nihJghwM5m7kxkQD7UvrWyHkLy8=
</data>
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json</key>
<data>
gcwBsH4BgyFY4sVtNt+/xOKS3vY=
</data>
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.swiftdoc</key>
<data>
YPtkDrAuBiPPEp4ZdRdBVlFXnRM=
</data>
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.swiftmodule</key>
<data>
9cIInnjJzJFtY+CZm2iNo5qL3MQ=
</data>
<key>Modules/module.modulemap</key>
<data>
cnpvYzvLIwWcxkQodj5uLbHkyRk=
</data>
</dict>
<key>files2</key>
<dict>
<key>Headers/StosJIT-Swift.h</key>
<dict>
<key>hash2</key>
<data>
1obIr4IjMvtcyNyYIV/Nh/5wahcA1cFjc4n4XVlNt2I=
</data>
</dict>
<key>Headers/StosJIT.h</key>
<dict>
<key>hash2</key>
<data>
yY9KyrRdOYRdlb7G6wVMU2hogasXMjwV5r8jUIk44ok=
</data>
</dict>
<key>Headers/idevice.h</key>
<dict>
<key>hash2</key>
<data>
zR9/TB9Dnv3uRC8qqGvaQ6c2yyOFUURmrHKLdEiUh/g=
</data>
</dict>
<key>Headers/plist.h</key>
<dict>
<key>hash2</key>
<data>
yFbGsiXBBp91tfsSFtS0Utt2Gpc3MEDFiMVXKG9q1rs=
</data>
</dict>
<key>Modules/StosJIT.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo</key>
<dict>
<key>hash2</key>
<data>
+Ehvco7cQbAaF7zufvBYTiGXFp37Hjym/Pav514sGPk=
</data>
</dict>
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json</key>
<dict>
<key>hash2</key>
<data>
Qnesa0n4URGWAopawg9bGx36dUwkYV00BoCJ8LFzlyg=
</data>
</dict>
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.swiftdoc</key>
<dict>
<key>hash2</key>
<data>
k7F2Xs2hh9iMbK8IE8TMtN6gjQ9kWs30NUKHeupq6VE=
</data>
</dict>
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.swiftmodule</key>
<dict>
<key>hash2</key>
<data>
gMDYNHcBPCNwZw2A5mEUiCyYAS9VhtQG0z+/WqAUrOQ=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
FGwGKs5SNvpCyiIWiOP4eml9m2e3KISmtCJVtNnUnUc=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@ -81,14 +81,14 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index) private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{ {
if ((uint)index > (uint)coefficients.Length) if ((uint)index < (uint)coefficients.Length)
{ {
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}"); return coefficients[index];
return 0;
} }
return coefficients[index]; Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -11,7 +11,7 @@ using Ryujinx.Audio.Renderer.Server.Sink;
using Ryujinx.Audio.Renderer.Server.Splitter; using Ryujinx.Audio.Renderer.Server.Splitter;
using Ryujinx.Audio.Renderer.Server.Types; using Ryujinx.Audio.Renderer.Server.Types;
using Ryujinx.Audio.Renderer.Server.Upsampler; using Ryujinx.Audio.Renderer.Server.Upsampler;
using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;

View File

@ -2,23 +2,17 @@ using ARMeilleure.Memory;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.InteropServices;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
namespace Ryujinx.Cpu.LightningJit.Cache namespace Ryujinx.Cpu.LightningJit.Cache
{ {
class WriteZeroCache : IDisposable class DualMappedNoWxCache : IDisposable
{ {
private const int CodeAlignment = 4; private const int CodeAlignment = 4; // Bytes.
private const int InitialCacheSize = 2 * 1024 * 1024; private const int SharedCacheSize = 512 * 1024 * 1024;
private const int GrowthCacheSize = 2 * 1024 * 1024; private const int LocalCacheSize = 128 * 1024 * 1024;
private const int MaxSharedCacheSize = 512 * 1024 * 1024;
private const int MaxLocalCacheSize = 128 * 1024 * 1024;
[DllImport("StosJIT.framework/StosJIT", EntryPoint = "writeZeroToMemory")]
public static extern bool WriteZeroToMemory(ulong addr, int length);
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there // How many calls to the same function we allow until we pad the shared cache to force the function to become available there
// and allow the guest to take the fast path. // and allow the guest to take the fast path.
@ -26,104 +20,24 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private class MemoryCache : IDisposable private class MemoryCache : IDisposable
{ {
private readonly ReservedRegion _region; private readonly DualMappedJitAllocator _allocator;
private readonly CacheMemoryAllocator _cacheAllocator; private readonly CacheMemoryAllocator _cacheAllocator;
public readonly IJitMemoryAllocator Allocator; public DualMappedJitAllocator Allocator => _allocator;
private readonly ulong _maxSize; public IntPtr RwPointer => _allocator.RwPtr;
private ulong _currentSize; public IntPtr RxPointer => _allocator.RxPtr;
private readonly Dictionary<int, HashSet<int>> _reusePages;
private readonly object _reuselock = new object();
public CacheMemoryAllocator CacheAllocator => _cacheAllocator; public CacheMemoryAllocator CacheAllocator => _cacheAllocator;
public IntPtr Pointer => _region.Block.Pointer; public IntPtr Pointer => _allocator.RwPtr;
public ulong CurrentSize => _currentSize;
public ulong MaxSize => _maxSize;
public MemoryCache(IJitMemoryAllocator allocator, ulong maxSize) public MemoryCache(ulong size)
{ {
Allocator = allocator; _allocator = new DualMappedJitAllocator(size);
_maxSize = maxSize; _cacheAllocator = new((int)size);
_currentSize = InitialCacheSize;
_region = new(allocator, maxSize);
_cacheAllocator = new((int)maxSize);
_reusePages = new Dictionary<int, HashSet<int>>();
_region.Block.MapAsRw(0, _currentSize);
_region.ExpandIfNeeded(_currentSize);
WriteZeroToMemory((ulong)_region.Block.Pointer.ToInt64(), (int)_currentSize);
}
public bool TryGetReusablePage(int size, out int offset)
{
lock (_reuselock)
{
if (_reusePages.TryGetValue(size, out var exactOffsets) && exactOffsets.Count > 0)
{
offset = exactOffsets.First();
exactOffsets.Remove(offset);
return true;
}
var largerSizes = _reusePages.Where(kvp => kvp.Key > size && kvp.Value.Count > 0)
.OrderBy(kvp => kvp.Key)
.FirstOrDefault();
if (largerSizes.Value != null && largerSizes.Value.Count > 0)
{
int largerSize = largerSizes.Key;
var largerOffsets = largerSizes.Value;
offset = largerOffsets.First();
largerOffsets.Remove(offset);
int remainingSize = largerSize - size;
if (remainingSize > 0)
{
AddReusablePage(offset + size, remainingSize);
}
return true;
}
offset = -1;
return false;
}
}
public void AddReusablePage(int offset, int size)
{
if (size < (int)MemoryBlock.GetPageSize())
{
return;
}
lock (_reuselock)
{
if (!_reusePages.TryGetValue(size, out var offsets))
{
offsets = new HashSet<int>();
_reusePages[size] = offsets;
}
offsets.Add(offset);
}
} }
public int Allocate(int codeSize) public int Allocate(int codeSize)
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
if (codeSize >= (int)MemoryBlock.GetPageSize() &&
(codeSize % (int)MemoryBlock.GetPageSize() == 0) &&
TryGetReusablePage(codeSize, out int reuseOffset))
{
ReprotectAsRw(reuseOffset, codeSize);
return reuseOffset;
}
int allocOffset = _cacheAllocator.Allocate(codeSize); int allocOffset = _cacheAllocator.Allocate(codeSize);
@ -132,63 +46,19 @@ namespace Ryujinx.Cpu.LightningJit.Cache
throw new OutOfMemoryException("JIT Cache exhausted."); throw new OutOfMemoryException("JIT Cache exhausted.");
} }
ulong requiredSize = (ulong)allocOffset + (ulong)codeSize;
if (requiredSize > _currentSize)
{
ulong neededGrowth = requiredSize - _currentSize;
ulong growthIncrements = (neededGrowth + GrowthCacheSize - 1) / GrowthCacheSize;
ulong newSize = _currentSize + (growthIncrements * GrowthCacheSize);
newSize = Math.Min(newSize, _maxSize);
if (newSize <= _currentSize || requiredSize > newSize)
{
throw new OutOfMemoryException("JIT Cache exhausted, cannot grow further.");
}
_region.Block.MapAsRw(_currentSize, newSize - _currentSize);
_region.ExpandIfNeeded(newSize);
WriteZeroToMemory((ulong)(_region.Block.Pointer.ToInt64() + (long)_currentSize), (int)(newSize - _currentSize));
_currentSize = newSize;
}
return allocOffset; return allocOffset;
} }
public void Free(int offset, int size) public void Free(int offset, int size)
{ {
if (size >= (int)MemoryBlock.GetPageSize() && (size % (int)MemoryBlock.GetPageSize() == 0) && _cacheAllocator.Free(offset, size);
(offset % (int)MemoryBlock.GetPageSize() == 0))
{
AddReusablePage(offset, size);
}
else
{
_cacheAllocator.Free(offset, size);
}
} }
public void ReprotectAsRw(int offset, int size) public void SysIcacheInvalidate(int offset, int size)
{ {
Debug.Assert(offset >= 0 && (offset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
Debug.Assert(size > 0 && (size & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
_region.Block.MapAsRw((ulong)offset, (ulong)size);
}
public void ReprotectAsRx(int offset, int size)
{
Debug.Assert(offset >= 0 && (offset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
Debug.Assert(size > 0 && (size & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
_region.Block.MapAsRx((ulong)offset, (ulong)size);
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{ {
JitSupportDarwin.SysIcacheInvalidate(_region.Block.Pointer + offset, size); JitSupportDarwin.SysIcacheInvalidate(_allocator.RxPtr + offset, size);
} }
else else
{ {
@ -196,14 +66,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
} }
public void ClearReusePool()
{
lock (_reuselock)
{
_reusePages.Clear();
}
}
private static int AlignCodeSize(int codeSize) private static int AlignCodeSize(int codeSize)
{ {
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
@ -213,8 +75,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{ {
if (disposing) if (disposing)
{ {
ClearReusePool(); _allocator.Dispose();
_region.Dispose();
_cacheAllocator.Clear(); _cacheAllocator.Clear();
} }
} }
@ -259,12 +120,12 @@ namespace Ryujinx.Cpu.LightningJit.Cache
[ThreadStatic] [ThreadStatic]
private static Dictionary<ulong, ThreadLocalCacheEntry> _threadLocalCache; private static Dictionary<ulong, ThreadLocalCacheEntry> _threadLocalCache;
public WriteZeroCache(IJitMemoryAllocator allocator, IStackWalker stackWalker, Translator translator) public DualMappedNoWxCache(IJitMemoryAllocator allocator, IStackWalker stackWalker, Translator translator)
{ {
_stackWalker = stackWalker; _stackWalker = stackWalker;
_translator = translator; _translator = translator;
_sharedCaches = new List<MemoryCache> { new(allocator, MaxSharedCacheSize) }; _sharedCaches = new List<MemoryCache> { new(SharedCacheSize) };
_localCaches = new List<MemoryCache> { new(allocator, MaxLocalCacheSize) }; _localCaches = new List<MemoryCache> { new(LocalCacheSize) };
_pendingMaps = new Dictionary<ulong, PageAlignedRangeList>(); _pendingMaps = new Dictionary<ulong, PageAlignedRangeList>();
_lock = new(); _lock = new();
} }
@ -275,7 +136,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
if (!_pendingMaps.TryGetValue(cacheKey, out var pendingMap)) if (!_pendingMaps.TryGetValue(cacheKey, out var pendingMap))
{ {
pendingMap = new PageAlignedRangeList( pendingMap = new PageAlignedRangeList(
(offset, size) => _sharedCaches[cacheIndex].ReprotectAsRx(offset, size), (offset, size) => _sharedCaches[cacheIndex].SysIcacheInvalidate(offset, size),
(address, func) => RegisterFunction(address, func)); (address, func) => RegisterFunction(address, func));
_pendingMaps[cacheKey] = pendingMap; _pendingMaps[cacheKey] = pendingMap;
} }
@ -304,15 +165,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
catch (OutOfMemoryException) catch (OutOfMemoryException)
{ {
// Try next cache
} }
} }
// All existing caches are full, create a new one
lock (_lock) lock (_lock)
{ {
var allocator = _sharedCaches[0].Allocator; var allocator = _sharedCaches[0].Allocator;
_sharedCaches.Add(new(allocator, MaxSharedCacheSize)); _sharedCaches.Add(new(SharedCacheSize));
return (_sharedCaches.Count - 1) << 28 | _sharedCaches[_sharedCaches.Count - 1].Allocate(codeLength); return (_sharedCaches.Count - 1) << 28 | _sharedCaches[_sharedCaches.Count - 1].Allocate(codeLength);
} }
} }
@ -327,14 +186,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
} }
catch (OutOfMemoryException) catch (OutOfMemoryException)
{ {
// Try next cache
} }
} }
lock (_lock) lock (_lock)
{ {
var allocator = _localCaches[0].Allocator; var allocator = _localCaches[0].Allocator;
_localCaches.Add(new(allocator, MaxLocalCacheSize)); _localCaches.Add(new(LocalCacheSize));
return (_localCaches.Count - 1) << 28 | _localCaches[_localCaches.Count - 1].Allocate(codeLength); return (_localCaches.Count - 1) << 28 | _localCaches[_localCaches.Count - 1].Allocate(codeLength);
} }
} }
@ -360,8 +219,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
MemoryCache cache = _sharedCaches[cacheIndex]; MemoryCache cache = _sharedCaches[cacheIndex];
funcPtr = cache.Pointer + funcOffset; funcPtr = cache.Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length)); code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
funcPtr = cache.RxPointer + funcOffset;
TranslatedFunction function = new(funcPtr, guestSize); TranslatedFunction function = new(funcPtr, guestSize);
@ -396,21 +255,22 @@ namespace Ryujinx.Cpu.LightningJit.Cache
Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0); Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0);
IntPtr funcPtr1 = _sharedCaches[cacheIndex].Pointer + funcOffset; IntPtr funcPtr1 = _sharedCaches[cacheIndex].Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr1, code.Length)); code.CopyTo(new Span<byte>((void*)funcPtr1, code.Length));
funcPtr1 = _sharedCaches[cacheIndex].RxPointer + funcOffset;
_sharedCaches[cacheIndex].ReprotectAsRx(funcOffset, sizeAligned); _sharedCaches[cacheIndex].SysIcacheInvalidate(funcOffset, sizeAligned);
return funcPtr1; return funcPtr1;
} }
catch (OutOfMemoryException) catch (OutOfMemoryException)
{ {
// Try next cache
} }
} }
var allocator = _sharedCaches[0].Allocator; var allocator = _sharedCaches[0].Allocator;
var newCache = new MemoryCache(allocator, MaxSharedCacheSize); var newCache = new MemoryCache(SharedCacheSize);
_sharedCaches.Add(newCache); _sharedCaches.Add(newCache);
cacheIndex = _sharedCaches.Count - 1; cacheIndex = _sharedCaches.Count - 1;
@ -425,8 +285,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
IntPtr funcPtr = newCache.Pointer + funcOffset; IntPtr funcPtr = newCache.Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length)); code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
funcPtr = newCache.RxPointer + funcOffset;
newCache.ReprotectAsRx(funcOffset, newSizeAligned); newCache.SysIcacheInvalidate(funcOffset, newSizeAligned);
return funcPtr; return funcPtr;
} }
@ -481,8 +342,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
for (int i = 0; i < _localCaches.Count; i++) for (int i = 0; i < _localCaches.Count; i++)
{ {
cachePointers[i] = _localCaches[i].Pointer; cachePointers[i] = _localCaches[i].RxPointer;
cacheSizes[i] = (int)_localCaches[i].CurrentSize; cacheSizes[i] = LocalCacheSize;
} }
IntPtr[] sharedPointers = new IntPtr[_sharedCaches.Count]; IntPtr[] sharedPointers = new IntPtr[_sharedCaches.Count];
@ -490,19 +351,20 @@ namespace Ryujinx.Cpu.LightningJit.Cache
for (int i = 0; i < _sharedCaches.Count; i++) for (int i = 0; i < _sharedCaches.Count; i++)
{ {
sharedPointers[i] = _sharedCaches[i].Pointer; sharedPointers[i] = _sharedCaches[i].RxPointer;
sharedSizes[i] = (int)_sharedCaches[i].CurrentSize; sharedSizes[i] = SharedCacheSize;
} }
// Iterate over the arrays and pass each element to GetCallStack
IEnumerable<ulong> callStack = null; IEnumerable<ulong> callStack = null;
for (int i = 0; i < _localCaches.Count; i++) for (int i = 0; i < _localCaches.Count; i++)
{ {
callStack = _stackWalker.GetCallStack( callStack = _stackWalker.GetCallStack(
framePointer, framePointer,
cachePointers[i], cachePointers[i], // Passing each individual cachePointer
cacheSizes[i], cacheSizes[i], // Passing each individual cacheSize
sharedPointers[i], sharedPointers[i], // Passing each individual sharedPointer
sharedSizes[i] sharedSizes[i] // Passing each individual sharedSize
); );
} }
@ -510,12 +372,16 @@ namespace Ryujinx.Cpu.LightningJit.Cache
foreach ((ulong address, ThreadLocalCacheEntry entry) in _threadLocalCache) foreach ((ulong address, ThreadLocalCacheEntry entry) in _threadLocalCache)
{ {
// We only want to delete if the function is already on the shared cache,
// otherwise we will keep translating the same function over and over again.
bool canDelete = !HasInAnyPendingMap(address); bool canDelete = !HasInAnyPendingMap(address);
if (!canDelete) if (!canDelete)
{ {
continue; continue;
} }
// We can only delete if the function is not part of the current thread call stack,
// otherwise we will crash the program when the thread returns to it.
foreach (ulong funcAddress in callStack) foreach (ulong funcAddress in callStack)
{ {
if (funcAddress >= (ulong)entry.FuncPtr && funcAddress < (ulong)entry.FuncPtr + (ulong)entry.Size) if (funcAddress >= (ulong)entry.FuncPtr && funcAddress < (ulong)entry.FuncPtr + (ulong)entry.Size)
@ -541,12 +407,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
var (cacheIndex, offset) = SplitCacheOffset(entry.Offset); var (cacheIndex, offset) = SplitCacheOffset(entry.Offset);
_localCaches[cacheIndex].Free(offset, sizeAligned); _localCaches[cacheIndex].Free(offset, sizeAligned);
_localCaches[cacheIndex].ReprotectAsRw(offset, sizeAligned);
} }
} }
public void ClearEntireThreadLocalCache() public void ClearEntireThreadLocalCache()
{ {
// Thread is exiting, delete everything.
if (_threadLocalCache == null) if (_threadLocalCache == null)
{ {
return; return;
@ -560,7 +428,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
var (cacheIndex, offset) = SplitCacheOffset(entry.Offset); var (cacheIndex, offset) = SplitCacheOffset(entry.Offset);
_localCaches[cacheIndex].Free(offset, sizeAligned); _localCaches[cacheIndex].Free(offset, sizeAligned);
_localCaches[cacheIndex].ReprotectAsRw(offset, sizeAligned);
} }
_threadLocalCache.Clear(); _threadLocalCache.Clear();
@ -577,10 +444,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
IntPtr funcPtr = _localCaches[cacheIndex].Pointer + funcOffset; IntPtr funcPtr = _localCaches[cacheIndex].Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length)); code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
funcPtr = _localCaches[cacheIndex].RxPointer + funcOffset;
(_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr, cacheIndex)); (_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr, cacheIndex));
_localCaches[cacheIndex].ReprotectAsRx(funcOffset, alignedSize); _localCaches[cacheIndex].SysIcacheInvalidate(funcOffset, alignedSize);
return funcPtr; return funcPtr;
} }

View File

@ -5,6 +5,7 @@ using System.Runtime.Versioning;
namespace Ryujinx.Cpu.LightningJit.Cache namespace Ryujinx.Cpu.LightningJit.Cache
{ {
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
static partial class JitSupportDarwin static partial class JitSupportDarwin
{ {
[LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")] [LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")]

View File

@ -41,7 +41,7 @@ namespace Ryujinx.Cpu.LightningJit
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs; private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
private readonly NoWxCache _noWxCache; private readonly NoWxCache _noWxCache;
private readonly WriteZeroCache _writeZeroCache; private readonly DualMappedNoWxCache _dualMappedCache;
private bool _disposed; private bool _disposed;
internal TranslatorCache<TranslatedFunction> Functions { get; } internal TranslatorCache<TranslatedFunction> Functions { get; }
@ -57,27 +57,14 @@ namespace Ryujinx.Cpu.LightningJit
if (IsNoWxPlatform) if (IsNoWxPlatform)
{ {
if (File.Exists("/System/Library/CoreServices/SystemVersion.plist")) string dualMapped = Environment.GetEnvironmentVariable("DUAL_MAPPED_JIT");
if (dualMapped == "1") //(OperatingSystem.IsIOSVersionAtLeast(19) || OperatingSystem.IsIOSVersionAtLeast(26))
{ {
string content = File.ReadAllText("/System/Library/CoreServices/SystemVersion.plist"); Console.WriteLine($"Dual Mapped JIT enabled.");
if (content.Contains("22E5200s") && content.Contains("18.4") && content.Contains("Beta")) _dualMappedCache = new(new JitMemoryAllocator(), CreateStackWalker(), this);
{ Functions = new TranslatorCache<TranslatedFunction>();
// iOS 18.4db1 (22E5200s) disables traditional JIT (R/X) and needs a debugger to fill to the page to make the executable region a debug map. FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
// Apple has confirmed that this change will be coming to later iOS releases. Stubs = new TranslatorStubs(FunctionTable, _dualMappedCache);
// Credit to JJTech for figuring out a workaround: https://gist.github.com/JJTech0130/142aee0f7bda9c61a421140d17afbdeb
Console.WriteLine($"User is using iOS 18.4db1 (22E5200s), enabling Debugger Memory Writing");
_writeZeroCache = new(new JitMemoryAllocator(), CreateStackWalker(), this);
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
Stubs = new TranslatorStubs(FunctionTable, _writeZeroCache);
}
else
{
_noWxCache = new(new JitMemoryAllocator(), CreateStackWalker(), this);
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
}
} }
else else
{ {
@ -125,7 +112,7 @@ namespace Ryujinx.Cpu.LightningJit
NativeInterface.UnregisterThread(); NativeInterface.UnregisterThread();
_noWxCache?.ClearEntireThreadLocalCache(); _noWxCache?.ClearEntireThreadLocalCache();
_writeZeroCache?.ClearEntireThreadLocalCache(); _dualMappedCache?.ClearEntireThreadLocalCache();
} }
internal IntPtr GetOrTranslatePointer(IntPtr framePointer, ulong address, ExecutionMode mode) internal IntPtr GetOrTranslatePointer(IntPtr framePointer, ulong address, ExecutionMode mode)
@ -135,10 +122,10 @@ namespace Ryujinx.Cpu.LightningJit
CompiledFunction func = Compile(address, mode); CompiledFunction func = Compile(address, mode);
return _noWxCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength); return _noWxCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength);
} }
else if (_writeZeroCache != null) else if (_dualMappedCache != null)
{ {
CompiledFunction func = Compile(address, mode); CompiledFunction func = Compile(address, mode);
return _writeZeroCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength); return _dualMappedCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength);
} }
return GetOrTranslate(address, mode).FuncPointer; return GetOrTranslate(address, mode).FuncPointer;
@ -239,9 +226,9 @@ namespace Ryujinx.Cpu.LightningJit
{ {
_noWxCache.Dispose(); _noWxCache.Dispose();
} }
else if (_writeZeroCache != null) else if (_dualMappedCache != null)
{ {
_writeZeroCache.Dispose(); _dualMappedCache.Dispose();
} }
else else
{ {

View File

@ -25,7 +25,7 @@ namespace Ryujinx.Cpu.LightningJit
private readonly AddressTable<ulong> _functionTable; private readonly AddressTable<ulong> _functionTable;
private readonly NoWxCache _noWxCache; private readonly NoWxCache _noWxCache;
private readonly WriteZeroCache _writeZeroCache; private readonly DualMappedNoWxCache _dualMappedCache;
private readonly GetFunctionAddressDelegate _getFunctionAddressRef; private readonly GetFunctionAddressDelegate _getFunctionAddressRef;
private readonly IntPtr _getFunctionAddress; private readonly IntPtr _getFunctionAddress;
@ -101,12 +101,12 @@ namespace Ryujinx.Cpu.LightningJit
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param> /// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <param name="writeZeroCache">Cache used on iOS versions that need a debugger to make a debug map</param> /// <param name="writeZeroCache">Cache used on iOS versions that need a debugger to make a debug map</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(AddressTable<ulong> functionTable, WriteZeroCache writeZeroCache) public TranslatorStubs(AddressTable<ulong> functionTable, DualMappedNoWxCache dualMappedCache)
{ {
ArgumentNullException.ThrowIfNull(functionTable); ArgumentNullException.ThrowIfNull(functionTable);
_functionTable = functionTable; _functionTable = functionTable;
_writeZeroCache = writeZeroCache; _dualMappedCache = dualMappedCache;
_getFunctionAddressRef = NativeInterface.GetFunctionAddress; _getFunctionAddressRef = NativeInterface.GetFunctionAddress;
_getFunctionAddress = Marshal.GetFunctionPointerForDelegate(_getFunctionAddressRef); _getFunctionAddress = Marshal.GetFunctionPointerForDelegate(_getFunctionAddressRef);
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
@ -131,7 +131,7 @@ namespace Ryujinx.Cpu.LightningJit
{ {
if (!_disposed) if (!_disposed)
{ {
if (_noWxCache == null) if (_noWxCache == null || _dualMappedCache == null)
{ {
if (_dispatchStub.IsValueCreated) if (_dispatchStub.IsValueCreated)
{ {
@ -383,9 +383,9 @@ namespace Ryujinx.Cpu.LightningJit
{ {
return _noWxCache.MapPageAligned(code); return _noWxCache.MapPageAligned(code);
} }
else if (_writeZeroCache != null) else if (_dualMappedCache != null)
{ {
return _writeZeroCache.MapPageAligned(code); return _dualMappedCache.MapPageAligned(code);
} }
else else
{ {

View File

@ -127,11 +127,12 @@ namespace Ryujinx.Cpu.Signal
ulong codeSizeAligned = BitUtils.AlignUp((ulong)code.Length, MemoryBlock.GetPageSize()); ulong codeSizeAligned = BitUtils.AlignUp((ulong)code.Length, MemoryBlock.GetPageSize());
_codeBlock = new MemoryBlock(codeSizeAligned); string dualMapped = Environment.GetEnvironmentVariable("DUAL_MAPPED_JIT");
_codeBlock = new MemoryBlock(codeSizeAligned, (dualMapped == "1") ? MemoryAllocationFlags.DualMapping : MemoryAllocationFlags.None);
_codeBlock.Write(0, code); _codeBlock.Write(0, code);
_codeBlock.Reprotect(0, codeSizeAligned, MemoryPermission.ReadAndExecute); _codeBlock.Reprotect(0, codeSizeAligned, MemoryPermission.ReadAndExecute);
return _codeBlock.Pointer; return _codeBlock.RxPointer;
} }
private static unsafe ref SignalHandlerConfig GetConfigRef() private static unsafe ref SignalHandlerConfig GetConfigRef()

View File

@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
public const int MaxShaderStages = 5; public const int MaxShaderStages = 5;
public const int MaxUniformBuffersPerStage = 18; public const int MaxUniformBuffersPerStage = 18;
public const int MaxStorageBuffersPerStage = 16; public const int MaxStorageBuffersPerStage = 16;
public const int MaxTexturesPerStage = 31; public const int MaxTexturesPerStage = 32; // 31
public const int MaxImagesPerStage = 16; public const int MaxImagesPerStage = 16;
public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages; public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages;
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages; public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;

View File

@ -680,6 +680,8 @@ namespace Ryujinx.Graphics.Vulkan
ShaderCollection program = _program; ShaderCollection program = _program;
// UpdateAndBindTexturesWithoutTemplate jas been renamed to UpdateAndBind and supports more then just textures.
if (_dirty.HasFlag(DirtyFlags.Uniform)) if (_dirty.HasFlag(DirtyFlags.Uniform))
{ {
if (program.UsePushDescriptors) if (program.UsePushDescriptors)
@ -688,51 +690,77 @@ namespace Ryujinx.Graphics.Vulkan
} }
else else
{ {
UpdateAndBind(cbs, program, PipelineBase.UniformSetIndex, pbp); try {
UpdateAndBind(cbs, program, PipelineBase.UniformSetIndex, pbp);
}
catch (Exception e)
{
// If binding fails, we can try to bind the uniform buffers without using the template.
// This is a workaround for some games that use invalid bindings.
UpdateAndBind(cbs, PipelineBase.UniformSetIndex, pbp);
}
} }
} }
if (_dirty.HasFlag(DirtyFlags.Storage)) if (_dirty.HasFlag(DirtyFlags.Storage))
{ {
UpdateAndBind(cbs, program, PipelineBase.StorageSetIndex, pbp); try {
UpdateAndBind(cbs, program, PipelineBase.StorageSetIndex, pbp);
}
catch (Exception e)
{
// If binding fails, we can try to bind the storage buffers without using the template.
// This is a workaround for some games that use invalid bindings.
UpdateAndBind(cbs, PipelineBase.StorageSetIndex, pbp);
}
} }
if (_dirty.HasFlag(DirtyFlags.Texture)) if (_dirty.HasFlag(DirtyFlags.Texture))
{ {
if (true) if (program.UpdateTexturesWithoutTemplate)
{ {
try UpdateAndBind(cbs, PipelineBase.TextureSetIndex, pbp);
{
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
catch (Exception e)
{
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
}
} }
else else
{ {
try try
{ {
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
} }
catch (Exception e) catch (Exception e)
{ {
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); // If binding fails, we can try to bind the textures without using the template.
// This is a workaround for some games that use invalid bindings.
UpdateAndBind(cbs, PipelineBase.TextureSetIndex, pbp);
} }
} }
} }
if (_dirty.HasFlag(DirtyFlags.Image)) if (_dirty.HasFlag(DirtyFlags.Image))
{ {
UpdateAndBind(cbs, program, PipelineBase.ImageSetIndex, pbp); try
{
UpdateAndBind(cbs, program, PipelineBase.ImageSetIndex, pbp);
}
catch (Exception e)
{
// If binding fails, we can try to bind the images without using the template.
// This is a workaround for some games that use invalid bindings.
UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp);
}
} }
if (program.BindingSegments.Length > PipelineBase.DescriptorSetLayouts) if (program.BindingSegments.Length > PipelineBase.DescriptorSetLayouts)
{ {
// Program is using extra sets, we need to bind those too. // Program is using extra sets, we need to bind those too.
BindExtraSets(cbs, program, pbp); // Ignore Extra Sets for now.
// BindExtraSets(cbs, program, pbp);
} }
_dirty = DirtyFlags.None; _dirty = DirtyFlags.None;
@ -764,7 +792,7 @@ namespace Ryujinx.Graphics.Vulkan
if (info.Buffer.Handle == 0) if (info.Buffer.Handle == 0)
{ {
info.Buffer = dummyBuffer?.Get(cbs).Value ?? default; info.Buffer = dummyBuffer?.Get(cbs).Value ?? default;
// info.Offset = 0; info.Offset = 0;
info.Range = Vk.WholeSize; info.Range = Vk.WholeSize;
} }
@ -962,9 +990,9 @@ namespace Ryujinx.Graphics.Vulkan
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp)
{ {
int setIndex = PipelineBase.TextureSetIndex; var program = _program;
var bindingSegments = program.BindingSegments[setIndex]; var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0) if (bindingSegments.Length == 0)
@ -972,6 +1000,24 @@ namespace Ryujinx.Graphics.Vulkan
return; return;
} }
var dummyBuffer = _dummyBuffer?.GetBuffer();
if (_updateDescriptorCacheCbIndex)
{
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
if (!program.HasMinimalLayout)
{
if (isNew)
{
Initialize(cbs, setIndex, dsc);
}
}
var dummyImageInfo = new DescriptorImageInfo var dummyImageInfo = new DescriptorImageInfo
{ {
ImageView = _dummyTexture.GetImageView().Get(cbs).Value, ImageView = _dummyTexture.GetImageView().Get(cbs).Value,
@ -979,89 +1025,160 @@ namespace Ryujinx.Graphics.Vulkan
ImageLayout = ImageLayout.General ImageLayout = ImageLayout.General
}; };
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
foreach (ResourceBindingSegment segment in bindingSegments) foreach (ResourceBindingSegment segment in bindingSegments)
{ {
int binding = segment.Binding; int binding = segment.Binding;
int count = segment.Count; int count = segment.Count;
if (!segment.IsArray) if (setIndex == PipelineBase.UniformSetIndex)
{ {
if (segment.Type != ResourceType.BufferTexture) for (int i = 0; i < count; i++)
{ {
for (int i = 0; i < count; i++) int index = binding + i;
if (_uniformSet.Set(index))
{ {
int index = binding + i; ref BufferRef buffer = ref _uniformBufferRefs[index];
ref var textureRef = ref _textureRefs[index];
var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView; bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler;
var imageInfo = new DescriptorImageInfo _uniformMirrored.Set(index, mirrored);
}
}
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
}
else if (setIndex == PipelineBase.StorageSetIndex)
{
for (int i = 0; i < count; i++)
{
int index = binding + i;
ref BufferRef buffer = ref _storageBufferRefs[index];
if (_storageSet.Set(index))
{
ref var info = ref _storageBuffers[index];
bool mirrored = UpdateBuffer(cbs,
ref info,
ref _storageBufferRefs[index],
dummyBuffer,
!buffer.Write && info.Range <= StorageBufferMaxMirrorable);
_storageMirrored.Set(index, mirrored);
}
}
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
}
else if (setIndex == PipelineBase.TextureSetIndex)
{
if (!segment.IsArray)
{
if (segment.Type != ResourceType.BufferTexture)
{
for (int i = 0; i < count; i++)
{ {
ImageView = imageView.Handle != 0 ? imageView : dummyImageInfo.ImageView, int index = binding + i;
Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler, ref var textureRef = ref _textureRefs[index];
ImageLayout = ImageLayout.General
};
dsc.UpdateImages(0, index, new[] { imageInfo }, DescriptorType.CombinedImageSampler); var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler;
var imageInfo = new DescriptorImageInfo
{
ImageView = imageView.Handle != 0 ? imageView : dummyImageInfo.ImageView,
Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler,
ImageLayout = ImageLayout.General
};
dsc.UpdateImages(0, index, new[] { imageInfo }, DescriptorType.CombinedImageSampler);
}
}
else
{
for (int i = 0; i < count; i++)
{
int index = binding + i;
var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default(BufferView);
dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer);
}
} }
} }
else else
{ {
for (int i = 0; i < count; i++) var arrayRef = _textureArrayRefs[binding];
if (segment.Type != ResourceType.BufferTexture)
{ {
int index = binding + i; var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler);
var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default; if (imageInfos != null)
dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer); {
for (int i = 0; i < imageInfos.Length && i < count; i++)
{
dsc.UpdateImages(0, binding + i, new[] { imageInfos[i] }, DescriptorType.CombinedImageSampler);
}
}
else
{
for (int i = 0; i < count; i++)
{
dsc.UpdateImages(0, binding + i, new[] { dummyImageInfo }, DescriptorType.CombinedImageSampler);
}
}
}
else
{
var bufferViews = arrayRef.Array.GetBufferViews(cbs);
if (bufferViews != null)
{
for (int i = 0; i < bufferViews.Length && i < count; i++)
{
dsc.UpdateBufferImages(0, binding + i, new[] { bufferViews[i] }, DescriptorType.UniformTexelBuffer);
}
}
else
{
for (int i = 0; i < count; i++)
{
dsc.UpdateBufferImages(0, binding + i, new[] { default(BufferView) }, DescriptorType.UniformTexelBuffer);
}
}
} }
} }
} }
else else if (setIndex == PipelineBase.ImageSetIndex)
{ {
var arrayRef = _textureArrayRefs[binding]; if (segment.Type != ResourceType.BufferImage)
if (segment.Type != ResourceType.BufferTexture)
{ {
var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler); Span<DescriptorImageInfo> images = _images;
if (imageInfos != null)
for (int i = 0; i < count; i++)
{ {
for (int i = 0; i < imageInfos.Length && i < count; i++) images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
{
dsc.UpdateImages(0, binding + i, new[] { imageInfos[i] }, DescriptorType.CombinedImageSampler);
}
}
else
{
for (int i = 0; i < count; i++)
{
dsc.UpdateImages(0, binding + i, new[] { dummyImageInfo }, DescriptorType.CombinedImageSampler);
}
} }
dsc.UpdateImages(0, binding, images[..count], DescriptorType.StorageImage);
} }
else else
{ {
var bufferViews = arrayRef.Array.GetBufferViews(cbs); Span<BufferView> bufferImages = _bufferImages;
if (bufferViews != null)
for (int i = 0; i < count; i++)
{ {
for (int i = 0; i < bufferViews.Length && i < count; i++) bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, true) ?? default;
{
dsc.UpdateBufferImages(0, binding + i, new[] { bufferViews[i] }, DescriptorType.UniformTexelBuffer);
}
}
else
{
for (int i = 0; i < count; i++)
{
dsc.UpdateBufferImages(0, binding + i, new[] { default(BufferView) }, DescriptorType.UniformTexelBuffer);
}
} }
dsc.UpdateBufferImages(0, binding, bufferImages[..count], DescriptorType.StorageTexelBuffer);
} }
} }
} }
var sets = dsc.GetSets(); var sets = dsc.GetSets();
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
} }
@ -1232,4 +1349,4 @@ namespace Ryujinx.Graphics.Vulkan
Dispose(true); Dispose(true);
} }
} }
} }

View File

@ -186,8 +186,6 @@ namespace Ryujinx.Graphics.Vulkan
return sets; return sets;
} }
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
DescriptorSetTemplate template = program.Templates[setIndex]; DescriptorSetTemplate template = program.Templates[setIndex];
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
@ -203,8 +201,6 @@ namespace Ryujinx.Graphics.Vulkan
templateUpdater.Commit(_gd, device, sets[0]); templateUpdater.Commit(_gd, device, sets[0]);
sets = dsc.GetSets();
return sets; return sets;
} }
} }

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device) public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
{ {
const int MaxAttachments = Constants.MaxRenderTargets + 1; int MaxAttachments = Constants.MaxRenderTargets + 1;
AttachmentDescription[] attachmentDescs = null; AttachmentDescription[] attachmentDescs = null;
@ -185,8 +185,8 @@ namespace Ryujinx.Graphics.Vulkan
if (gd.Capabilities.SupportsMultiView) if (gd.Capabilities.SupportsMultiView)
{ {
pipeline.ScissorsCount = Constants.MaxViewports; pipeline.ScissorsCount = (uint)Constants.MaxViewports;
pipeline.ViewportsCount = Constants.MaxViewports; pipeline.ViewportsCount = (uint)Constants.MaxViewports;
} }
else else
{ {

View File

@ -137,7 +137,7 @@ namespace Ryujinx.Graphics.Vulkan
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages); (IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows. // Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
UpdateTexturesWithoutTemplate = OperatingSystem.IsIOS(); // gd.IsQualcommProprietary && usesBufferTextures; UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
_compileTask = Task.CompletedTask; _compileTask = Task.CompletedTask;
_firstBackgroundUse = false; _firstBackgroundUse = false;

View File

@ -402,9 +402,7 @@ namespace Ryujinx.Graphics.Vulkan
properties.Limits.FramebufferDepthSampleCounts & properties.Limits.FramebufferDepthSampleCounts &
properties.Limits.FramebufferStencilSampleCounts; properties.Limits.FramebufferStencilSampleCounts;
bool isDynamicStateSupported = OperatingSystem.IsIOS() bool isDynamicStateSupported = OperatingSystem.IsIOSVersionAtLeast(17) && _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName);
? OperatingSystem.IsIOSVersionAtLeast(17) && _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName)
: _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName);
Capabilities = new HardwareCapabilities( Capabilities = new HardwareCapabilities(
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"), _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
@ -422,8 +420,8 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.ShaderStorageImageMultisample, features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName), _physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
isDynamicStateSupported, isDynamicStateSupported,
features2.Features.MultiViewport && !IsMoltenVk, // Workaround for AMD on MoltenVK issue features2.Features.MultiViewport, // && !IsMoltenVk, // Workaround for AMD on MoltenVK issue
!IsMoltenVk ? featuresRobustness2.NullDescriptor : false, featuresRobustness2.NullDescriptor && !IsMoltenVk,
supportsPushDescriptors && !IsMoltenVk, supportsPushDescriptors && !IsMoltenVk,
propertiesPushDescriptor.MaxPushDescriptors, propertiesPushDescriptor.MaxPushDescriptors,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
@ -722,7 +720,7 @@ namespace Ryujinx.Graphics.Vulkan
SystemMemoryType memoryType; SystemMemoryType memoryType;
if (IsSharedMemory && !IsMoltenVk) if (IsSharedMemory)
{ {
memoryType = SystemMemoryType.UnifiedMemory; memoryType = SystemMemoryType.UnifiedMemory;
} }
@ -784,10 +782,10 @@ namespace Ryujinx.Graphics.Vulkan
imageSetIndex: PipelineBase.ImageSetIndex, imageSetIndex: PipelineBase.ImageSetIndex,
extraSetBaseIndex: PipelineBase.DescriptorSetLayouts, extraSetBaseIndex: PipelineBase.DescriptorSetLayouts,
maximumExtraSets: Math.Max(0, (int)limits.MaxBoundDescriptorSets - PipelineBase.DescriptorSetLayouts), maximumExtraSets: Math.Max(0, (int)limits.MaxBoundDescriptorSets - PipelineBase.DescriptorSetLayouts),
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage, maximumUniformBuffersPerStage: (uint)Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage, maximumStorageBuffersPerStage: (uint)Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage, maximumTexturesPerStage: (uint)Constants.MaxTexturesPerStage,
maximumImagesPerStage: Constants.MaxImagesPerStage, maximumImagesPerStage: (uint)Constants.MaxImagesPerStage,
maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize, maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize,
maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy, maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy,
shaderSubgroupSize: (int)Capabilities.SubgroupSize, shaderSubgroupSize: (int)Capabilities.SubgroupSize,

View File

@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
_generalServiceDetail = new GeneralServiceDetail _generalServiceDetail = new GeneralServiceDetail
{ {
ClientId = GeneralServiceManager.Count, ClientId = GeneralServiceManager.Count,
IsAnyInternetRequestAccepted = true, // NOTE: Why not accept any internet request? IsAnyInternetRequestAccepted = false, // NOTE: Why not accept any internet request?
}; };
NetworkChange.NetworkAddressChanged += LocalInterfaceCacheHandler; NetworkChange.NetworkAddressChanged += LocalInterfaceCacheHandler;

View File

@ -992,15 +992,16 @@ namespace Ryujinx.Headless.SDL2
bool isNintendoStyle = true; // gamepadName.Contains("Nintendo") || gamepadName.Contains("Joycons"); bool isNintendoStyle = true; // gamepadName.Contains("Nintendo") || gamepadName.Contains("Joycons");
ControllerType currentController; ControllerType currentController;
if (index == PlayerIndex.Handheld) if (index == PlayerIndex.Handheld)
{ {
currentController = ControllerType.Handheld; currentController = ControllerType.Handheld;
} }
else if (gamepadName.Contains("Joycons") || gamepadName.Contains("Backbone")) else if (gamepadName.Contains("Joycons") || gamepadName.Contains("Backbone"))
{ {
currentController = ControllerType.JoyconPair; currentController = ControllerType.JoyconPair;
} }
else else
{ {
currentController = ControllerType.ProController; currentController = ControllerType.ProController;
} }

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
@ -48,7 +48,7 @@
<Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/> <Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
<ItemGroup> <ItemGroup>
<!-- <LinkerArg Include="-Wl,-ld_classic" /> --> <!-- <LinkerArg Include="-Wl,-ld_classic" /> -->
<LinkerArg Include="-flto -Ofast" /> <LinkerArg Include="-flto -Ofast" />
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22" <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
Condition=" $(RuntimeIdentifier.Contains('simulator')) "/> Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>

View File

@ -0,0 +1,53 @@
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Ryujinx.Common.Logging;
namespace Ryujinx.Memory
{
/// <summary>
/// Placeholder class for JIT memory allocation on iOS.
/// Intended to allocate memory with both r/x and r/w permissions,
/// as a workaround for stricter W^X (Write XOR Execute) enforcement introduced in iOS 26.
///
/// Specifically targets iOS 26, where the traditional method of reprotecting
/// memory from writable to executable (RX) no longer works for JIT code.
///
/// The actual allocation logic will be implemented after the release of iOS 26
/// to reduce the risk of this workaround being patched.
/// </summary>
public class DualMappedJitAllocator : IDisposable
{
public IntPtr RwPtr { get; private set; }
public IntPtr RxPtr { get; private set; }
public ulong Size { get; private set; }
private IntPtr _mmapPtr;
public DualMappedJitAllocator(ulong size)
{
var stackTrace = new StackTrace(1, false);
var callingMethod = stackTrace.GetFrame(0)?.GetMethod();
Logger.Info?.Print(LogClass.Cpu,
$"Allocating dual-mapped JIT memory of size {size} bytes, called by {callingMethod?.DeclaringType?.FullName}.{callingMethod?.Name}");
Size = size;
AllocateDualMapping();
}
private void AllocateDualMapping()
{
RwPtr = IntPtr.Zero;
RxPtr = IntPtr.Zero;
}
public void Dispose()
{
}
}
}

View File

@ -48,5 +48,11 @@ namespace Ryujinx.Memory
/// On some platforms, this requires special flags to be passed that will allow the memory to be executable. /// On some platforms, this requires special flags to be passed that will allow the memory to be executable.
/// </summary> /// </summary>
Jit = 1 << 5, Jit = 1 << 5,
/// <summary>
/// Indicates that the memory will be used to store JIT generated code in both read and execute modes.
/// On some platforms, this is required to allow the JIT to generate code that can be executed.
/// </summary>
DualMapping = 1 << 6,
} }
} }

View File

@ -13,14 +13,21 @@ namespace Ryujinx.Memory
private readonly bool _isMirror; private readonly bool _isMirror;
private readonly bool _viewCompatible; private readonly bool _viewCompatible;
private readonly bool _forJit; private readonly bool _forJit;
private DualMappedJitAllocator _dualMappedAllocator;
private IntPtr _sharedMemory; private IntPtr _sharedMemory;
private IntPtr _pointer; private IntPtr _pointer;
private IntPtr _rxPointer;
/// <summary> /// <summary>
/// Pointer to the memory block data. /// Pointer to the memory block data (RW).
/// </summary> /// </summary>
public IntPtr Pointer => _pointer; public IntPtr Pointer => _pointer;
/// <summary>
/// Pointer to the RX mapping (for execution), or IntPtr.Zero if not dual-mapped.
/// </summary>
public IntPtr RxPointer => _rxPointer;
/// <summary> /// <summary>
/// Size of the memory block. /// Size of the memory block.
/// </summary> /// </summary>
@ -35,7 +42,16 @@ namespace Ryujinx.Memory
/// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception> /// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception>
public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None) public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None)
{ {
if (flags.HasFlag(MemoryAllocationFlags.Mirrorable)) Size = size;
if (flags.HasFlag(MemoryAllocationFlags.DualMapping))
{
_dualMappedAllocator = new DualMappedJitAllocator(size);
_pointer = _dualMappedAllocator.RwPtr;
_rxPointer = _dualMappedAllocator.RxPtr;
_forJit = true;
return;
}
else if (flags.HasFlag(MemoryAllocationFlags.Mirrorable))
{ {
_sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve)); _sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve));
@ -58,7 +74,7 @@ namespace Ryujinx.Memory
_pointer = MemoryManagement.Allocate(size, _forJit); _pointer = MemoryManagement.Allocate(size, _forJit);
} }
Size = size; _rxPointer = _pointer;
} }
/// <summary> /// <summary>
@ -165,7 +181,10 @@ namespace Ryujinx.Memory
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception> /// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true) public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true)
{ {
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, throwOnFail); if (_rxPointer == _pointer)
{
MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, throwOnFail);
}
} }
/// <summary> /// <summary>
@ -388,8 +407,13 @@ namespace Ryujinx.Memory
{ {
IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero); IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero);
// If pointer is null, the memory was already freed or never allocated. if (_dualMappedAllocator != null)
if (ptr != IntPtr.Zero) {
_dualMappedAllocator.Dispose();
_dualMappedAllocator = null;
_rxPointer = IntPtr.Zero;
}
else if (ptr != IntPtr.Zero)
{ {
if (_usesSharedMemory) if (_usesSharedMemory)
{ {