1
0
forked from MeloNX/MeloNX

Compare commits

...

10 Commits

64 changed files with 2995 additions and 5790 deletions

131
README.md
View File

@ -12,108 +12,118 @@
<p align="center">
MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices.
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">MeloNX license (Based on MIT)</a>. <br
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">MeloNX license</a>. <br
</p>
# Compatibility
MeloNX works on iPhone XS/XR and later and iPad 8th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>.
MeloNX works on iPhone 11 (XS/XR may work but can have issues) and later and iPad 8th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>.
# Usage
## Paid Certificates are **NOT** supported and we will not give any help when using them.
## FAQ
- MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but may have issues or not work at all.
- MeloNX cannot be Sideloaded normally and requires the use of the following Installation Guide(s).
- [SideStore](https://sidestore.io/) is recommended for Sideloading MeloNX
- Apple ID with free / paid developer account
- MeloNX requires JIT
- Recommended Device: iPhone 15 Pro or newer.
- Low-End Recommended Device: iPhone 13 Pro.
## Discord Server
We have a discord server!
- https://discord.gg/melonx
## How to install
### Paid Developer Account
1. **Sideload the App**
- Use any sideloading tool that supports Apple IDs.
#### 1. Sideload MeloNX
Download and install MeloNX using your preferred Apple ID sideloader:
- [Download MeloNX from Releases](https://git.ryujinx.app/melonx/emu/-/releases)
2. **Enable Entitlements**
#### 2. Enable Memory Entitlement
- Visit [Apple Developer Identifiers](https://developer.apple.com/account/resources/identifiers).
- Locate **MeloNX** and enable the following entitlements:
- `Increased Memory Limit`
- `Increased Debugging Memory Limit`
3. **Reinstall the App**
- Delete the existing installation.
- Sideload the app again with the updated entitlements.
#### 3. Reinstall MeloNX
- Delete existing MeloNX installation
- Sideload MeloNX again
- Verify **Increased Memory Limit** is enabled in app
#### 4. Setup Files
- Add Encryption Keys and Firmware using the file picker inside MeloNX
- If having Issues installing firmware:
- You can Install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
4. **Enable JIT**
- Use your preferred method to enable Just-In-Time (JIT) compilation.
- We reccomend using [StikDebug](https://apps.apple.com/us/app/stikdebug/id6744045754)
#### 5. Enable JIT
- Enable JIT using your preferred method. We recommend [StikDebug](https://apps.apple.com/us/app/stikdebug/id6744045754).
5. **Add Necessary Files**
If having Issues installing firmware (Make sure your Keys are installed first)
- If needed, install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
### Free Developer Account (Experimental)
### Free Developer Account
1. **Sideload MeloNX**
- Use [SideStore](https://sidestore.io/) or [AltStore](https://altstore.io/) (**NOT** AltStore PAL).
***The Entitlement App is **NOT** needed for AltStore Classic***
- You may skip Step 2 and Step 3
2. **Sideload the Entitlement App**
- Install [this app](https://github.com/hugeBlack/GetMoreRam/releases/download/nightly/Entitlement.ipa) using [SideStore](https://sidestore.io/) or [AltStore](https://altstore.io/) (**NOT** AltStore PAL).
#### 1. Sideload Applications
Download and install both apps using your preferred **APPLE ID** sideloader:
- **MeloNX**: [Download from Releases](https://git.ryujinx.app/melonx/emu/-/releases)
- **Entitlement App**: [Download IPA](https://github.com/hugeBlack/GetMoreRam/releases/download/nightly/Entitlement.ipa)
3. **Sign In to Your Account**
- Open **Settings** in the entitlement app and sign in with your Apple ID.
#### 2. Enable Memory Entitlement
- Open the **Entitlement app** > **Settings**
- Sign in with your Apple ID
- Go to **App IDs** > tap **Refresh**
- Select **MeloNX** (e.g., "com.stossy11.MeloNX.XXXXXX")
- Tap **Add Increased Memory Limit**
4. **Refresh App IDs**
- Navigate to the **App IDs** page.
- Tap **Refresh** to update the list.
#### 3. Reinstall MeloNX
- Delete existing MeloNX installation
- Sideload MeloNX again
- Verify **Increased Memory Limit** is enabled in app
5. **Enable Increased Memory Limit**
- Select **MeloNX** (should be like "com.stossy11.MeloNX" or some variation) from the list.
- Tap **Add Increased Memory Limit**.
#### 4. Setup Files
- Add Encryption Keys and Firmware using the file picker inside MeloNX
- If having Issues installing firmware:
- You can Install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
6. **Reinstall MeloNX**
- Delete the existing installation.
- Sideload the app again using SideStore or AltStore.
7. **Verify Increased Memory Limit**
- Open MeloNX and check if the **Increased Memory Limit** is enabled.
8. **Add Necessary Files**
If having Issues installing firmware (Make sure your keys are installed first)
- If needed, install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
9. **Enable JIT**
- Use your preferred method to enable Just-In-Time (JIT) compilation.
- We recommend using [StikDebug](https://apps.apple.com/us/app/stikdebug/id6744045754)
#### 5. Enable JIT
- Enable JIT using your preferred method. We recommend [StikDebug](https://apps.apple.com/us/app/stikdebug/id6744045754).
### TrollStore
As Said in FAQ:
> MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but may have issues or not work at all.
1. **Install MeloNX with TrollStore**
#### 1. Install MeloNX
- Use TrollStore to install MeloNX.
2. **Add Necessary Files**
#### 2. Setup Files
- Add Encryption Keys and Firmware using the file picker inside MeloNX
- If having Issues installing firmware:
- You can Install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
3. **Enable TrollStore JIT**
- MeloNX includes automatic JIT using the TrollStore URL Scheme
- Open MeloNX Settings
- Scroll down and enable the "TrollStore JIT" toggle
#### 2. Enable TrollStore JIT
- Open **Settings** inside **MeloNX**
- Under **Misc**, scroll down and enable the **"TrollStore" toggle**
- Profit
### Free Developer Account (Xcode)
**NOTE: These Xcode builds are nightly and may have unfinished features.**
1. **Compile Guide**
- Visit the [guide here](https://git.743378673.xyz/MeloNX/MeloNX/src/branch/XC-ios-ht/Compile.md).
- Visit the [guide here](https://git.ryujinx.app/melonx/emu/-/blob/XC-ios-ht/Compile.md?ref_type=heads).
2. **Add Necessary Files**
@ -141,12 +151,12 @@ If having Issues installing firmware (Make sure your keys are installed first)
- **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
The GPU emulator emulates the Switch's Maxwell GPU using Metal (via MoltenVK) APIs through a custom build of Silk.NET.
- **Input**
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers.
Motion controls are natively supported in most cases.
We currently have support for keyboard, touch input, JoyCon input support, and nearly all MFI controllers.
Motion controls are natively supported in most cases, however JoyCons do not have motion support doe to an iOS limitation.
- **DLC & Modifications**
@ -157,14 +167,13 @@ If having Issues installing firmware (Make sure your keys are installed first)
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
## License
# License
This software is licensed under the terms of the [MeloNX license (Based on MIT License)](LICENSE.txt).
This software is licensed under the terms of the [MeloNX license](LICENSE.txt).
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
## Credits
# Credits
- [Ryujinx](https://github.com/ryujinx-mirror/ryujinx) is used for the base of this emulator. (link is to ryujinx-mirror since they were supportive)
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.

View File

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

View File

@ -28,6 +28,7 @@
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
7CD054B52E253D2F00287A89 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 7CD054B42E253D2F00287A89 /* SwiftUIIntrospect */; };
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
/* End PBXBuildFile section */
@ -46,13 +47,6 @@
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
remoteInfo = MeloNX;
};
4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
proxyType = 1;
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
};
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
@ -109,6 +103,10 @@
CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = {
"Dependencies/Dynamic Libraries/BreakpointJIT.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/Hypervisor.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
@ -120,10 +118,6 @@
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/StosJIT.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (
CodeSignOnCopy,
);
@ -172,13 +166,13 @@
};
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
membershipExceptions = (
"Dependencies/Dynamic Libraries/BreakpointJIT.framework",
"Dependencies/Dynamic Libraries/Hypervisor.framework",
"Dependencies/Dynamic Libraries/libavcodec.dylib",
"Dependencies/Dynamic Libraries/libavutil.dylib",
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
"Dependencies/Dynamic Libraries/RyujinxHelper.framework",
"Dependencies/Dynamic Libraries/StosJIT.framework",
Dependencies/XCFrameworks/libavcodec.xcframework,
Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework,
@ -203,6 +197,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7CD054B52E253D2F00287A89 /* SwiftUIIntrospect in Frameworks */,
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
4549A31C2DD8795900EC8D88 /* CocoaAsyncSocket in Frameworks */,
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
@ -294,7 +289,6 @@
buildRules = (
);
dependencies = (
4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4E80A98F2CD6F54500029585 /* MeloNX */,
@ -303,6 +297,7 @@
packageProductDependencies = (
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
4549A31B2DD8795900EC8D88 /* CocoaAsyncSocket */,
7CD054B42E253D2F00287A89 /* SwiftUIIntrospect */,
);
productName = MeloNX;
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
@ -395,6 +390,7 @@
packageReferences = (
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
4549A31A2DD8795900EC8D88 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */,
7CD054B32E253D2F00287A89 /* XCRemoteSwiftPackageReference "swiftui-introspect" */,
);
preferredProjectObjectVersion = 56;
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
@ -492,11 +488,6 @@
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
};
4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
targetProxy = 4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */;
};
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
@ -655,7 +646,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
DEVELOPMENT_TEAM = D59DHVRS87;
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = NO;
@ -791,6 +782,13 @@
"$(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;
GENERATE_INFOPLIST_FILE = YES;
@ -1046,9 +1044,19 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
@ -1071,7 +1079,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
DEVELOPMENT_TEAM = D59DHVRS87;
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = YES;
@ -1207,6 +1215,13 @@
"$(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;
GENERATE_INFOPLIST_FILE = YES;
@ -1462,9 +1477,19 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
@ -1679,6 +1704,14 @@
kind = branch;
};
};
7CD054B32E253D2F00287A89 /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/siteline/swiftui-introspect.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.3.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@ -1692,6 +1725,11 @@
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
productName = SwiftSVG;
};
7CD054B42E253D2F00287A89 /* SwiftUIIntrospect */ = {
isa = XCSwiftPackageProductDependency;
package = 7CD054B32E253D2F00287A89 /* XCRemoteSwiftPackageReference "swiftui-introspect" */;
productName = SwiftUIIntrospect;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 4E80A9852CD6F54500029585 /* Project object */;

View File

@ -1,5 +1,5 @@
{
"originHash" : "b4a593815773c4e9eedb98cabe88f41620776314bffb6c39d5a41cb743e4d390",
"originHash" : "ae170c69ea1ccacb2f3982b1a91b689a09bc4dcc59ed970f1df977bf7c7aed6f",
"pins" : [
{
"identity" : "cocoaasyncsocket",
@ -18,6 +18,15 @@
"branch" : "master",
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
}
},
{
"identity" : "swiftui-introspect",
"kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/swiftui-introspect.git",
"state" : {
"revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336",
"version" : "1.3.0"
}
}
],
"version" : 3

View File

@ -65,6 +65,7 @@
debugDocumentVersioning = "YES"
debugXPCServices = "NO"
debugServiceExtension = "internal"
enableGPUFrameCaptureMode = "3"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES"
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_syswm.h>
#include <StosJIT/StosJIT-Swift.h>
#include "MobileGestalt.h"
#ifdef __cplusplus
extern "C" {

View File

@ -17,7 +17,11 @@ func isJITEnabled() -> Bool {
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 {
@ -62,7 +66,6 @@ func allocateTest() -> Bool {
memcpy(jitMemory, code, code.count)
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
return false
}
@ -71,3 +74,26 @@ func allocateTest() -> Bool {
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

@ -22,21 +22,21 @@ class NativeController: Hashable, BaseController {
init(_ controller: GCController) {
nativeController = controller
controllerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
var ncontrollerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
let vendorName = nativeController.vendorName ?? "Unknown"
var usesdeviceHaptics = (ncontrollerHaptics == nil && (vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one"))
controllerHaptics = usesdeviceHaptics ? try? CHHapticEngine() : ncontrollerHaptics
// Make sure the haptic engine exists before attempting to start it or initialize the controller.
if let hapticsEngine = controllerHaptics {
do {
try hapticsEngine.start()
rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 2.5)
// print("CHHapticEngine started and RumbleController initialized.")
rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: usesdeviceHaptics ? 2.0 : 2.5)
} catch {
// print("Error starting CHHapticEngine: \(error.localizedDescription)")
rumbleController = nil
}
} else {
// print("CHHapticEngine is nil. Cannot initialize RumbleController.")
rumbleController = nil
}
setupHandheldController()
@ -49,26 +49,24 @@ class NativeController: Hashable, BaseController {
internal func tryRegisterMotion(slot: UInt8) {
// Setup Motion
let dsuServer = DSUServer.shared
let vendorName = nativeController.vendorName ?? "Unknown"
var usesdevicemotion = (vendorName.lowercased() == "Joy-Con (l/R)".lowercased() || vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one")
if nativeController.vendorName?.lowercased() == "Joy-Con (l/R)".lowercased() {
deviceMotionProvider = DeviceMotionProvider(slot: slot)
if let provider = deviceMotionProvider {
dsuServer.register(provider)
}
} else {
controllerMotionProvider = ControllerMotionProvider(controller: nativeController, slot: slot)
if let provider = controllerMotionProvider {
dsuServer.register(provider)
}
usesdevicemotion ? (deviceMotionProvider = DeviceMotionProvider(slot: slot)) : (controllerMotionProvider = ControllerMotionProvider(controller: nativeController, slot: slot))
if let provider = controllerMotionProvider {
dsuServer.register(provider)
} else if let provider = deviceMotionProvider {
dsuServer.register(provider)
}
}
internal func tryGetMotionProvider() -> DSUMotionProvider? {
if nativeController.vendorName == "Joy-Con (l/R)" {
if let deviceMotionProvider {
return deviceMotionProvider
} else {
return controllerMotionProvider
}
return controllerMotionProvider
}
private func setupHandheldController() {
@ -94,38 +92,39 @@ class NativeController: Hashable, BaseController {
},
SetPlayerIndex: { userdata, playerIndex in
// print("Player index set to \(playerIndex)")
guard let userdata, let player = GCControllerPlayerIndex(rawValue: Int(playerIndex)) else { return }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
_self.nativeController.playerIndex = player
},
Rumble: { userdata, lowFreq, highFreq in
// print("Rumble with \(lowFreq), \(highFreq)")
guard let userdata else { return 0 }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
_self.rumbleController?.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
return 0
},
RumbleTriggers: { userdata, leftRumble, rightRumble in
// print("Trigger rumble with \(leftRumble), \(rightRumble)")
return 0
},
SetLED: { userdata, red, green, blue in
// print("Set LED to RGB(\(red), \(green), \(blue))")
guard let userdata else { return 0 }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
guard let light = _self.nativeController.light else { return 0 }
light.color = .init(red: Float(red), green: Float(green), blue: Float(blue))
return 0
},
SendEffect: { userdata, data, size in
// print("Effect sent with size \(size)")
return 0
}
)
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)
if instanceID < 0 {
// print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
return
}
controller = SDL_GameControllerOpen(Int32(instanceID))
if controller == nil {
// print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
return
}
@ -214,7 +213,6 @@ class NativeController: Hashable, BaseController {
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
guard controller != nil else { return }
// // print("Button: \(button.rawValue) {state: \(state)}")
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
let value: Int = (state == 1) ? 32767 : 0

View File

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

View File

@ -99,10 +99,7 @@ extension Notification.Name {
static let newLogCaptured = Notification.Name("newLogCaptured")
}
struct Controller: Identifiable, Hashable {
var id: String
var name: String
}
struct iOSNav<Content: View>: View {
@ViewBuilder var content: () -> Content
@ -136,6 +133,7 @@ class Ryujinx : ObservableObject {
@Published var emulationUIView: MeloMTKView? = nil
@Published var config: Ryujinx.Arguments? = nil
@Published var games: [Game] = []
@Published var aspectRatio: AspectRatio = .fixed16x9
@Published var defMLContentSize: CGFloat?
@ -240,7 +238,7 @@ class Ryujinx : ObservableObject {
disablevsync: Bool = false,
language: SystemLanguage = .americanEnglish,
regioncode: SystemRegionCode = .usa,
handHeldController: Bool = false,
handHeldController: Bool = false
) {
self.gamepath = gamepath
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 {
let exceptionType: String
let message: String
@ -563,7 +597,11 @@ class Ryujinx : ObservableObject {
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])
@ -835,116 +873,8 @@ public extension UIDevice {
return identifier + String(UnicodeScalar(UInt8(value)))
}
func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity
#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)
return CallMGCopyAnswer(kMGPhysicalHardwareNameString)?.takeUnretainedValue() as? String ?? 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,83 @@
//
// NavigationItemPalette.swift
// iTorrent
//
// Created by Daniil Vinogradov on 14.11.2024.
//
import UIKit
import SwiftUI
@_spi(Advanced) import SwiftUIIntrospect
public extension View {
func navigationItemBottomPalette(@ViewBuilder body: () -> (some View)) -> some View {
modifier(NavitaionItemBottomPaletteContent(content: body().asController))
}
}
struct NavitaionItemBottomPaletteContent: ViewModifier {
let content: UIViewController
func body(content: Content) -> some View {
content
.introspect(.viewController, on: .iOS(.v14...), customize: { viewController in
let view = self.content.view!
view.backgroundColor = .clear
let size = view.systemLayoutSizeFitting(.init(width: viewController.view.frame.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
viewController.navigationItem.setBottomPalette(view, height: size.height)
})
}
}
extension UINavigationItem {
func setBottomPalette(_ contentView: UIView?, height: CGFloat = 44) {
/// "_setBottomPalette:"
let selector = NSSelectorFromBase64String("X3NldEJvdHRvbVBhbGV0dGU6")
guard responds(to: selector) else { return }
perform(selector, with: Self.makeNavigationItemPalette(with: contentView, height: height))
}
private static func makeNavigationItemPalette(with contentView: UIView?, height: CGFloat) -> UIView? {
guard let contentView else { return nil }
contentView.translatesAutoresizingMaskIntoConstraints = false
let contentViewHolder = UIView(frame: .init(x: 0, y: 0, width: 0, height: height))
contentViewHolder.autoresizingMask = [.flexibleHeight]
contentViewHolder.addSubview(contentView)
NSLayoutConstraint.activate([
contentViewHolder.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
contentViewHolder.topAnchor.constraint(equalTo: contentView.topAnchor),
contentView.trailingAnchor.constraint(equalTo: contentViewHolder.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: contentViewHolder.bottomAnchor),
])
/// "_UINavigationBarPalette"
guard let paletteClass = NSClassFromBase64String("X1VJTmF2aWdhdGlvbkJhclBhbGV0dGU=") as? UIView.Type
else { return nil }
/// "alloc"
/// "initWithContentView:"
guard let palette = paletteClass.perform(NSSelectorFromBase64String("YWxsb2M="))
.takeUnretainedValue()
.perform(NSSelectorFromBase64String("aW5pdFdpdGhDb250ZW50Vmlldzo="), with: contentViewHolder)
.takeUnretainedValue() as? UIView
else { return nil }
palette.preservesSuperviewLayoutMargins = true
return palette
}
}
func NSSelectorFromBase64String(_ base64String: String) -> Selector {
NSSelectorFromString(String(base64: base64String))
}
func NSClassFromBase64String(_ aBase64ClassName: String) -> AnyClass? {
NSClassFromString(String(base64: aBase64ClassName))
}
extension String {
init(base64: String) {
self.init(data: Data(base64Encoded: base64)!, encoding: .utf8)!
}
}

View File

@ -0,0 +1,36 @@
//
// UIKitSwiftUIInarop.swift
// iTorrent
//
// Created by Daniil Vinogradov on 01/11/2023.
//
import SwiftUI
private struct GenericControllerView: UIViewControllerRepresentable {
let viewController: UIViewController
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Context) -> UIViewController {
viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { /* Ignore */ }
}
extension View {
@MainActor
var asController: UIHostingController<Self> {
let vc = UIHostingController<Self>(rootView: self)
if #available(iOS 16.4, *) {
vc.safeAreaRegions = []
}
return vc
}
}
extension UIViewController {
var asView: some View {
GenericControllerView(viewController: self)
}
}

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

View File

@ -9,7 +9,7 @@
import SwiftUI
struct JoystickController: View {
@State var iscool: Bool? = nil
@State var iscool: Bool
@Environment(\.colorScheme) var colorScheme
@Binding var showBackground: Bool
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
@ -25,15 +25,8 @@ struct JoystickController: View {
}
public var body: some View {
VStack {
Joystick(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)
}
}
Group {
Joystick(right: iscool, position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground)
}
}
}

View File

@ -16,8 +16,7 @@ struct EmulationView: View {
@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 isAirplaying = Air.shared.connected
@Binding var startgame: Game?
@ -27,9 +26,18 @@ struct EmulationView: View {
@State var showSettings = false
@State var pauseEmu = true
@AppStorage("location-enabled") var locationenabled: Bool = false
@FocusState private var isFocused: Bool
@ObservedObject var ryujijnx = Ryujinx.shared
var body: some View {
ZStack {
if oldView {
Color.black
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
.allowsHitTesting(false)
}
if isAirplaying {
TouchView()
.ignoresSafeArea()
@ -37,104 +45,108 @@ struct EmulationView: View {
.onAppear {
Air.play(AnyView(MetalView().ignoresSafeArea().edgesIgnoringSafeArea(.all)))
}
Color.black
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
.allowsHitTesting(false)
} else {
MetalView() // The Emulation View
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
MetalViewContainer() // The Emulation View
}
// Above Emulation View
if isVCA {
ControllerView() // Virtual Controller
ControllerView(isEditing: .constant(false), gameId: startgame?.titleId) // Virtual Controller
.opacity(controllerOpacity)
.allowsHitTesting(true)
}
Group {
VStack {
HStack {
if performacehud, !showlogsgame {
PerformanceOverlayView()
}
Spacer()
if performacehud, showlogsgame {
PerformanceOverlayView()
}
VStack {
HStack {
if !performacehud, showlogsgame, ProcessInfo.processInfo.isLowPowerModeEnabled {
Circle()
.fill(Color.orange)
.frame(width: 10, height: 10)
.padding()
}
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")
}
if ssb {
Menu {
Button {
pause_emulation(pauseEmu)
pauseEmu.toggle()
} label: {
Label {
Text(pauseEmu ? "Pause" : "Play")
} icon: {
Image(systemName: pauseEmu ? "pause.circle" : "play.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")
}
Menu() {
ForEach(AspectRatio.allCases.filter { $0 != ryujijnx.aspectRatio }, id: \.self) { ratio in
Button {
ryujijnx.aspectRatio = ratio
} label: {
Label {
Text(ratio.displayName)
} icon: {
Image(systemName: "rectangle.expand.vertical")
}
}
}
} label: {
ExtButtonIconView(button: .guide, opacity: 0.4)
Label {
Text(ryujijnx.aspectRatio.displayName)
} icon: {
Image(systemName: "rectangle.expand.vertical")
}
}
.padding()
Spacer()
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()
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 {
DispatchQueue.main.async {
isFocused = true
}
LocationManager.sharedInstance.startUpdatingLocation()
Air.shared.connectionCallbacks.append { cool in
DispatchQueue.main.async {
@ -148,10 +160,12 @@ struct EmulationView: View {
print(cool)
startgame = nil
stop_emulation()
try? Ryujinx.shared.stop()
try? ryujijnx.stop()
}
}
}
.onKeyPress()
.focused($isFocused)
.onChange(of: scenePhase) { newPhase in
// Detect when the app enters the background
if newPhase == .background {
@ -173,3 +187,20 @@ struct EmulationView: View {
}
}
}
// 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

@ -35,7 +35,6 @@ class InGameSettingsManager: PerGameSettingsManaging {
Ryujinx.shared.config = config[currentgame.titleId]
let args = Ryujinx.shared.buildCommandLineArgs(from: config[currentgame.titleId] ?? Ryujinx.Arguments())
// Convert Arguments to ones that Ryujinx can Read
let cArgs = args.map { strdup($0) }
defer { cArgs.forEach { free($0) } }
var argvPtrs = cArgs

View File

@ -14,12 +14,21 @@ struct PerformanceOverlayView: View {
var body: some View {
VStack {
Text("\(fpsmonitor.formatFPS())")
.foregroundStyle(.white)
.stroke(color: .black, width: 2)
Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage))
.foregroundStyle(.white)
.stroke(color: .black, width: 2)
if ProcessInfo.processInfo.isLowPowerModeEnabled {
Text("\(fpsmonitor.formatFPS())")
.foregroundStyle(.white)
.stroke(color: .orange, width: 2)
Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage))
.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,154 @@
//
// 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 {
Color.black
stretchedView(containerSize: geo.size)
} else if oldView {
Color.black
oldStyleView(containerSize: geo.size)
} else {
modernView(containerSize: geo.size)
}
}
.animation(.easeInOut(duration: 0.3), value: ryujinx.aspectRatio)
.animation(.easeInOut(duration: 0.3), value: oldView)
}
.ignoresSafeArea()
}
// MARK: - View Components
private func stretchedView(containerSize: CGSize) -> some View {
let size = targetSize(for: containerSize, ignoreSafeArea: true)
return MetalView()
.frame(width: size.width, height: size.height)
}
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, ignoreSafeArea: Bool = false) -> 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 16.0 / 9.0
}
}()
let safeArea: UIEdgeInsets = ignoreSafeArea ? .zero : UIApplication.shared.windows.first?.safeAreaInsets ?? .zero
let adjustedContainer = CGSize(
width: containerSize.width - safeArea.left - safeArea.right,
height: containerSize.height - safeArea.top - safeArea.bottom
)
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")
guard !disabled else { return }
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
setAspectRatio(Ryujinx.shared.aspectRatio)
for touch in touches {
let location = touch.location(in: self)
@ -153,7 +153,7 @@ class MeloMTKView: MTKView {
let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
guard !disabled else { return }
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
setAspectRatio(Ryujinx.shared.aspectRatio)
for touch in touches {
if ignoredTouches.contains(touch) {

View File

@ -21,14 +21,13 @@ struct MetalView: UIViewRepresentable {
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()
set_native_window(layerPtr)
Ryujinx.shared.emulationUIView = view
Ryujinx.shared.metalLayer = metalLayer
return view
@ -51,5 +50,7 @@ struct MetalView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: Context) {
// nothin
print(context)
}
}

View File

@ -10,8 +10,7 @@ import MetalKit
struct TouchView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = MeloMTKView()
return view
return MeloMTKView()
}
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_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: 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
@AppStorage("showlogsloading") var showlogsloading: Bool = true
@ -79,9 +82,13 @@ struct ContentView: View {
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
]
_settings = State(initialValue: defaultSettings)
if #available(iOS 19, *) {
setenv("HAS_TXM", ProcessInfo.processInfo.hasTXM ? "1" : "0", 1)
} else {
setenv("HAS_TXM", "0", 1)
}
// print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue)
_settings = State(initialValue: defaultSettings)
initializeSDL()
}
@ -132,7 +139,6 @@ struct ContentView: View {
JITPopover() {
ryujinx.jitenabled = false
}
// .interactiveDismissDisabled()
}
}
@ -149,15 +155,10 @@ struct ContentView: View {
let _ = loadSettings()
isLoading = true
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
refreshControllersList()
}
refreshControllersList()
UserDefaults.standard.set(false, forKey: "lockInApp")
// print(MTLHud.shared.isEnabled)
initControllerObservers()
Air.play(AnyView(
@ -166,7 +167,6 @@ struct ContentView: View {
refreshControllersList()
ryujinx.addGames()
checkJitStatus()
@ -287,7 +287,6 @@ struct ContentView: View {
queue: .main
) { notification in
if let controller = notification.object as? GCController {
// print("Controller connected: \(controller.productCategory)")
nativeControllers[controller] = .init(controller)
refreshControllersList()
}
@ -299,7 +298,8 @@ struct ContentView: View {
queue: .main
) { notification in
if let controller = notification.object as? GCController {
// print("Controller disconnected: \(controller.productCategory)")
currentControllers = []
controllersList = []
nativeControllers[controller]?.cleanup()
nativeControllers[controller] = nil
refreshControllersList()
@ -316,6 +316,9 @@ struct ContentView: View {
}
private func refreshControllersList() {
currentControllers = []
controllersList = []
controllersList = ryujinx.getConnectedControllers()
if let onscreen = controllersList.first(where: { $0.name == ryujinx.virtualController.controllername }) {
@ -324,11 +327,11 @@ struct ContentView: View {
controllersList.removeAll(where: { $0.id == "0" || (!$0.name.starts(with: "GC - ") && $0 != onscreencontroller) })
controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") }
currentControllers = []
if controllersList.count == 1 {
currentControllers.append(controllersList[0])
if !ProcessInfo.processInfo.isiOSAppOnMac {
currentControllers.append(controllersList[0])
}
} else if (controllersList.count - 1) >= 1 {
for controller in controllersList {
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
@ -398,6 +401,12 @@ struct ContentView: View {
if syncqsubmits {
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() {
@ -411,7 +420,11 @@ struct ContentView: View {
if jitStreamerEB {
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 useTrollStore {
@ -421,12 +434,7 @@ struct ContentView: View {
} else if jitStreamerEB {
enableJITEB()
} else {
if !allocateTest(), checkDebugged() {
loop_heartbeat()
sleep(5)
let cool = String(cString: attach(getpid())!)
print(cool)
}
// nothing
}
}
}
@ -435,8 +443,6 @@ struct ContentView: View {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
components.host == "game" {
refreshControllersList()
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
game = ryujinx.games.first(where: { $0.titleId == text })
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {

View File

@ -96,9 +96,15 @@ struct GameInfoSheet: View {
.navigationTitle(game.titleName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
presentationMode.wrappedValue.dismiss()
ToolbarItem(placement: .topBarTrailing) {
if #available(iOS 26, *) {
Button(role: .close) {
presentationMode.wrappedValue.dismiss()
}
} else {
Button("Dismiss") {
presentationMode.wrappedValue.dismiss()
}
}
}
}

View File

@ -27,13 +27,21 @@ struct GameLibraryView: View {
@State var isSelectingGameFile = false
@State var isViewingGameInfo: Bool = false
@State var gamePerGameSettings: Game?
@State var gameController: Game?
var isShowingPerGameSettings: Binding<Bool> {
Binding<Bool> {
gamePerGameSettings != nil
} set: { value in
!value ? gamePerGameSettings = nil : ()
}
}
var isShowingGameController: Binding<Bool> {
Binding<Bool> {
gameController != nil
} set: { value in
!value ? gameController = nil : ()
}
}
@State var isSelectingGameUpdate: Bool = false
@State var isSelectingGameDLC: Bool = false
@ -70,28 +78,38 @@ struct GameLibraryView: View {
var body: some View {
iOSNav {
ZStack {
// Background color
Color(UIColor.systemBackground)
.ignoresSafeArea()
VStack(spacing: 0) {
// Header with stats
if !Ryujinx.shared.games.isEmpty {
GameLibraryHeader(
totalGames: Ryujinx.shared.games.count,
recentGames: realRecentGames.count,
firmwareVersion: firmwareversion
)
}
// Game list
if Ryujinx.shared.games.isEmpty {
EmptyGameLibraryView(isSelectingGameFile: $isSelectingGameFile)
} else {
gameListView
.animation(.easeInOut(duration: 0.3), value: searchText)
}
Group {
// Game list
if Ryujinx.shared.games.isEmpty {
EmptyGameLibraryView(isSelectingGameFile: $isSelectingGameFile)
} else {
gameListView
.animation(.easeInOut(duration: 0.3), value: searchText)
}
}
.navigationItemBottomPalette {
if !Ryujinx.shared.games.isEmpty {
GameLibraryHeader(
totalGames: Ryujinx.shared.games.count,
recentGames: realRecentGames.count,
firmwareVersion: firmwareversion
)
.overlay(Group {
if ryujinx.jitenabled {
VStack {
HStack {
Spacer()
Circle()
.frame(width: 12, height: 12)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.foregroundColor(Color.green)
.padding()
}
Spacer()
}
}
})
}
}
.navigationTitle("Game Library")
@ -156,27 +174,11 @@ struct GameLibraryView: View {
}
}
}
.overlay(Group {
if ryujinx.jitenabled {
VStack {
HStack {
Spacer()
Circle()
.frame(width: 12, height: 12)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.foregroundColor(Color.green)
.padding()
}
Spacer()
}
}
})
.onChange(of: startemu) { game in
guard let game else { return }
addToRecentGames(game)
}
// .searchable(text: $searchText, placement: .toolbar, prompt: "Search games or developers")
.searchable(text: $searchText, placement: .toolbar, prompt: "Search games or developers")
.onChange(of: searchText) { _ in
isSearching = !searchText.isEmpty
}
@ -213,6 +215,9 @@ struct GameLibraryView: View {
.sheet(isPresented: isShowingPerGameSettings) {
PerGameSettingsView(titleId: gamePerGameSettings!.titleId)
}
.fullScreenCover(isPresented: isShowingGameController) {
ControllerView(isEditing: isShowingGameController, gameId: gameController?.titleId)
}
.sheet(isPresented: Binding(
get: { isViewingGameInfo && gameInfo != nil },
set: { newValue in
@ -233,7 +238,7 @@ struct GameLibraryView: View {
private var gameListView: some View {
ScrollView {
LazyVStack(spacing: 0) {
LazyVStack(alignment: .leading, spacing: 0) {
if !isSearching && !realRecentGames.isEmpty {
// Recent Games Section
VStack(alignment: .leading, spacing: 0) {
@ -273,42 +278,27 @@ struct GameLibraryView: View {
.foregroundColor(.primary)
.padding(.horizontal)
.padding(.top)
ForEach(filteredGames) { game in
GameListRow(
game: game,
startemu: $startemu,
games: games,
isViewingGameInfo: $isViewingGameInfo,
isSelectingGameUpdate: $isSelectingGameUpdate,
isSelectingGameDLC: $isSelectingGameDLC,
gameRequirements: $gameRequirements,
gameInfo: $gameInfo,
perGameSettings: $gamePerGameSettings
)
.padding(.horizontal)
.padding(.vertical, 8)
}
}
}
} else {
ForEach(filteredGames) { game in
GameListRow(
game: game,
startemu: $startemu,
games: games,
isViewingGameInfo: $isViewingGameInfo,
isSelectingGameUpdate: $isSelectingGameUpdate,
isSelectingGameDLC: $isSelectingGameDLC,
gameRequirements: $gameRequirements,
gameInfo: $gameInfo,
perGameSettings: $gamePerGameSettings
)
.padding(.horizontal)
.padding(.vertical, 8)
}
}
ForEach(filteredGames) { game in
GameListRow(
game: game,
startemu: $startemu,
games: games,
isViewingGameInfo: $isViewingGameInfo,
isSelectingGameUpdate: $isSelectingGameUpdate,
isSelectingGameDLC: $isSelectingGameDLC,
gameRequirements: $gameRequirements,
gameInfo: $gameInfo,
isShowingGameController: $gameController,
perGameSettings: $gamePerGameSettings
)
.padding(.horizontal)
.padding(.vertical, 8)
}
Spacer(minLength: 50)
}
}
@ -502,6 +492,12 @@ struct GameLibraryView: View {
} label: {
Label("\(game.titleName) Settings", systemImage: "gear")
}
Button {
gameController = game
} label: {
Label("Controller Layout", systemImage: "formfitting.gamecontroller")
}
}
Section {
@ -527,6 +523,12 @@ struct GameLibraryView: View {
Label("Remove from Recents", systemImage: "trash")
}
Button(role: .destructive) {
Ryujinx.clearShaderCache(game.titleId)
} label: {
Label("Clear Shader Cache", systemImage: "trash")
}
if #available(iOS 15, *) {
Button(role: .destructive) {
deleteGame(game: game)
@ -629,7 +631,7 @@ struct GameLibraryHeader: View {
// Stats cards
StatCard(
icon: "gamecontroller.fill",
title: "Total Games",
title: "Games",
value: "\(totalGames)",
color: .blue
)
@ -649,8 +651,7 @@ struct GameLibraryHeader: View {
)
}
.padding(.horizontal)
.padding(.top, 8)
.padding(.bottom, 4)
.padding(.bottom, 8)
}
}
@ -659,7 +660,9 @@ struct StatCard: View {
let title: String
let value: String
let color: Color
@Namespace var statCardIdNamespace
var body: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
@ -675,11 +678,24 @@ struct StatCard: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background(color.opacity(0.1))
.cornerRadius(10)
.apply {
if #available(iOS 26, *) {
$0
.glassEffect(.regular.tint(color.opacity(0.05)), in: RoundedRectangle(cornerRadius: 16))
.glassEffectID("StatCardID", in: statCardIdNamespace)
} else {
$0
.background(color.opacity(0.1))
.cornerRadius(10)
}
}
}
}
extension View {
func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) }
}
// MARK: - Game Card View
struct GameCardView: View {
let game: Game
@ -797,6 +813,7 @@ struct GameListRow: View {
@Binding var isSelectingGameDLC: Bool
@Binding var gameRequirements: [GameRequirements]
@Binding var gameInfo: Game?
@Binding var isShowingGameController: Game?
@StateObject private var settingsManager = PerGameSettingsManager.shared
@Binding var perGameSettings: Game?
@State var gametoDelete: Game?
@ -855,20 +872,11 @@ struct GameListRow: View {
}
}
}
if $settingsManager.config.wrappedValue.contains(where: { $0.key == game.titleId }) {
Image(systemName: "gearshape.circle")
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundStyle(.blue)
.frame(width: 20, height: 20)
}
Spacer()
VStack(alignment: .leading) {
VStack(alignment: .trailing) {
// Compatibility badges
HStack {
if let gameReq = gameRequirements.first(where: { $0.game_id == game.titleId }) {
let totalMemory = ProcessInfo.processInfo.physicalMemory
@ -902,6 +910,15 @@ struct GameListRow: View {
.layoutPriority(1)
}
}
HStack {
if $settingsManager.config.wrappedValue.contains(where: { $0.key == game.titleId }) {
Image(systemName: "gearshape.circle")
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundStyle(.blue)
.frame(width: 20, height: 20)
}
// Play button
Image(systemName: "play.circle.fill")
@ -915,57 +932,6 @@ struct GameListRow: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.contentShape(Rectangle())
.contextMenu {
Section {
Button {
startemu = game
} label: {
Label("Play Now", systemImage: "play.fill")
}
Button {
gameInfo = game
isViewingGameInfo.toggle()
if game.titleName.lowercased() == "portal" || game.titleName.lowercased() == "portal 2" {
gamepo = true
}
} label: {
Label("Game Info", systemImage: "info.circle")
}
Button {
perGameSettings = game
} label: {
Label("\(game.titleName) Settings", systemImage: "gear")
}
}
Section {
Button {
gameInfo = game
isSelectingGameUpdate.toggle()
} label: {
Label("Update Manager", systemImage: "arrow.up.circle")
}
Button {
gameInfo = game
isSelectingGameDLC.toggle()
} label: {
Label("DLC Manager", systemImage: "plus.circle")
}
}
Section {
Button(role: .destructive) {
gametoDelete = game
showGameDeleteConfirmation.toggle()
} label: {
Label("Delete", systemImage: "trash")
}
}
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
gametoDelete = game
@ -1002,6 +968,71 @@ struct GameListRow: View {
}
.listRowInsets(EdgeInsets())
.wow(colorScheme)
.contextMenu {
Section {
Button {
startemu = game
} label: {
Label("Play Now", systemImage: "play.fill")
}
Button {
gameInfo = game
isViewingGameInfo.toggle()
if game.titleName.lowercased() == "portal" || game.titleName.lowercased() == "portal 2" {
gamepo = true
}
} label: {
Label("Game Info", systemImage: "info.circle")
}
Button {
perGameSettings = game
} label: {
Label("\(game.titleName) Settings", systemImage: "gear")
}
// isShowingGameController
Button {
isShowingGameController = game
} label: {
Label("Controller Layout", systemImage: "formfitting.gamecontroller")
}
}
Section {
Button {
gameInfo = game
isSelectingGameUpdate.toggle()
} label: {
Label("Update Manager", systemImage: "arrow.up.circle")
}
Button {
gameInfo = game
isSelectingGameDLC.toggle()
} label: {
Label("DLC Manager", systemImage: "plus.circle")
}
}
Section {
Button(role: .destructive) {
Ryujinx.clearShaderCache(game.titleId)
} label: {
Label("Clear Shader Cache", systemImage: "trash")
}
Button(role: .destructive) {
gametoDelete = game
showGameDeleteConfirmation.toggle()
} label: {
Label("Delete", systemImage: "trash")
}
}
}
} else {
Button(action: {
startemu = game
@ -1114,6 +1145,12 @@ struct GameListRow: View {
} label: {
Label("Game Info", systemImage: "info.circle")
}
Button {
isShowingGameController = game
} label: {
Label("Controller Layout", systemImage: "formfitting.gamecontroller")
}
}
Section {
@ -1133,6 +1170,12 @@ struct GameListRow: View {
}
Section {
Button(role: .destructive) {
Ryujinx.clearShaderCache(game.titleId)
} label: {
Label("Clear Shader Cache", systemImage: "trash")
}
Button {
gametoDelete = game
showGameDeleteConfirmation.toggle()
@ -1238,30 +1281,10 @@ func pullGameCompatibility(completion: @escaping (Result<[GameRequirements], Err
extension View {
func wow(_ colorScheme: ColorScheme) -> some View {
if #available(iOS 26.0, *) {
return self
.glassEffect(Glass.regular, in:
RoundedRectangle(cornerRadius: 12)
)
} else {
return self
.background(
RoundedRectangle(cornerRadius: 12)
.fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5))
)
}
self
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color(uiColor: .secondarySystemGroupedBackground))
)
}
}
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

@ -168,50 +168,44 @@ struct PerGameSettingsView: View {
var body: some View {
iOSNav {
ZStack {
Color(UIColor.systemBackground)
.ignoresSafeArea()
VStack(spacing: 0) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(PerSettingsCategory.allCases, id: \.id) { category in
CategoryButton(
title: category.rawValue,
icon: category.icon,
isSelected: selectedCategory == category
) {
selectedCategory = category
}
}
}
.padding(.horizontal)
.padding(.vertical, 8)
}
Divider()
// Settings content
ScrollView {
VStack(spacing: 24) {
switch selectedCategory {
case .graphics:
graphicsSettings
.padding(.top)
case .system:
systemSettings
.padding(.top)
case .advanced:
advancedSettings
.padding(.top)
// Settings content
ScrollView {
VStack(spacing: 24) {
switch selectedCategory {
case .graphics:
graphicsSettings
.padding(.top)
case .system:
systemSettings
.padding(.top)
case .advanced:
advancedSettings
.padding(.top)
}
Spacer(minLength: 50)
}
.padding(.bottom)
}
.scrollDismissesKeyboardIfAvailable()
Spacer(minLength: 50)
}
.padding(.bottom)
}
.background(Color(uiColor: .systemGroupedBackground))
.scrollDismissesKeyboardIfAvailable()
.navigationItemBottomPalette {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(PerSettingsCategory.allCases, id: \.id) { category in
CategoryButton(
title: category.rawValue,
icon: category.icon,
isSelected: selectedCategory == category
) {
selectedCategory = category
}
}
}
.defaultScrollAnchorIsAvailable(.center)
.padding(.horizontal)
.padding(.vertical, 8)
}
}
.navigationTitle("Settings")

View File

@ -232,6 +232,7 @@ struct SettingsViewNew: View {
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: 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
@ -260,20 +261,18 @@ struct SettingsViewNew: View {
@AppStorage("disableTouch") var disableTouch = false
@AppStorage("disableTouch") var blackScreen = false
@AppStorage("location-enabled") var locationenabled: Bool = false
@AppStorage("runOnMainThread") var runOnMainThread = false
@AppStorage("oldSettingsUI") var oldSettingsUI = false
@AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState()
let totalMemory = ProcessInfo.processInfo.physicalMemory
@AppStorage("lockInApp") var restartApp = false
@AppStorage("OldView") var oldView = true
@State private var showResolutionInfo = false
@State private var showAnisotropicInfo = false
@State private var showControllerInfo = false
@ -375,12 +374,26 @@ struct SettingsViewNew: View {
color: .blue
)
InfoCard(
title: "System",
value: "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)",
icon: "applelogo",
color: .gray
)
let versionPart = ProcessInfo.processInfo.operatingSystemVersionString.replacingOccurrences(of: "Version ", with: "")
let parts = versionPart.components(separatedBy: " (Build ")
if parts.count == 2 {
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(
title: "Increased Memory Limit",
@ -456,58 +469,32 @@ struct SettingsViewNew: View {
var iOSSettings: some View {
iOSNav {
ZStack {
// Background color
Color(UIColor.systemBackground)
.ignoresSafeArea()
VStack(spacing: 0) {
// Category selector
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(SettingsCategory.allCases, id: \.id) { category in
CategoryButton(
title: category.rawValue,
icon: category.icon,
isSelected: selectedCategory == category
) {
selectedCategory = category
}
}
}
// Settings content
ScrollView {
VStack(spacing: 24) {
deviceInfoCard
.padding(.horizontal)
.padding(.vertical, 8)
.padding(.top)
switch selectedCategory {
case .graphics:
graphicsSettings
case .input:
inputSettings
case .system:
systemSettings
case .advanced:
advancedSettings
case .misc:
miscSettings
}
Divider()
// Settings content
ScrollView {
VStack(spacing: 24) {
deviceInfoCard
.padding(.horizontal)
.padding(.top)
switch selectedCategory {
case .graphics:
graphicsSettings
case .input:
inputSettings
case .system:
systemSettings
case .advanced:
advancedSettings
case .misc:
miscSettings
}
Spacer(minLength: 50)
}
.padding(.bottom)
}
.scrollDismissesKeyboardIfAvailable()
Spacer(minLength: 50)
}
.padding(.bottom)
}
.scrollDismissesKeyboardIfAvailable()
.background(Color(uiColor: .systemGroupedBackground))
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.large)
// .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
@ -520,6 +507,25 @@ struct SettingsViewNew: View {
settingsManager.saveSettings()
}
}
.navigationItemBottomPalette {
// Category selector
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(SettingsCategory.allCases, id: \.id) { category in
CategoryButton(
title: category.rawValue,
icon: category.icon,
isSelected: selectedCategory == category
) {
selectedCategory = category
}
}
}
.defaultScrollAnchorIsAvailable(.center)
.padding(.horizontal)
.padding(.vertical, 8)
}
}
}
}
@ -551,6 +557,16 @@ struct SettingsViewNew: View {
.font(.subheadline)
.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)")
.font(.subheadline)
.foregroundColor(.secondary)
@ -755,6 +771,11 @@ struct SettingsViewNew: View {
// Aspect ratio card
SettingsCard {
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")
.font(.headline)
@ -825,16 +846,6 @@ struct SettingsViewNew: View {
Divider()
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 +1253,7 @@ struct SettingsViewNew: View {
SettingsSection(title: "Miscellaneous Options") {
SettingsCard {
VStack(spacing: 4) {
if UIDevice.current.userInterfaceIdiom == .pad {
SettingsToggle(isOn: $toggleGreen, icon: "arrow.clockwise", label: "Toggle Color Green when \"ON\"")
@ -1254,11 +1266,6 @@ struct SettingsViewNew: View {
Divider()
if colorScheme == .light {
SettingsToggle(isOn: $blackScreen, icon: "iphone.slash", label: "Black Screen when using AirPlay")
Divider()
}
Button {
showAppIconSwitcher = true
@ -1354,20 +1361,25 @@ struct SettingsViewNew: View {
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")
if ryujinx.firmwareversion != "0" {
Divider()
Button {
Ryujinx.shared.removeFirmware()
} label: {
HStack {
Text("Remove Firmware")
.foregroundColor(.blue)
Spacer()
}
.padding(.vertical, 8)
Divider()
Button {
Ryujinx.clearShaderCache()
} label: {
HStack {
Image(systemName: "trash")
.foregroundColor(.blue)
Text("Clear All Shader Cache")
.foregroundColor(.primary)
Spacer()
}
.padding(.vertical, 8)
}
}
}
@ -1498,12 +1510,23 @@ struct CategoryButton: View {
}
.foregroundColor(isSelected ? .blue : .secondary)
.frame(width: 70, height: 56)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(isSelected ? Color.blue.opacity(0.15) : Color.clear)
)
.animation(.bouncy(duration: 0.3), value: isSelected)
}
.apply {
if #available(iOS 26, *) {
if isSelected {
$0.glassEffect(.regular.tint(Color.blue.opacity(0.15)), in: RoundedRectangle(cornerRadius: 16))
} else {
$0
}
} else {
$0.background(
RoundedRectangle(cornerRadius: 12)
.fill(isSelected ? Color.blue.opacity(0.15) : Color.clear)
)
}
}
}
}
@ -1542,7 +1565,7 @@ struct SettingsCard<Content: View>: View {
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(colorScheme == .dark ? Color(.systemGray6) : Color.white)
.fill(Color(.secondarySystemGroupedBackground))
.shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2)
)
.padding(.horizontal)
@ -1667,3 +1690,14 @@ extension View {
}
}
// this code is used to enable the keyboard to be dismissed when scrolling if available on iOS 16+
extension View {
@ViewBuilder
func defaultScrollAnchorIsAvailable(_ anchor: UnitPoint?) -> some View {
if #available(iOS 17.0, *) {
self.defaultScrollAnchor(anchor)
} else {
self
}
}
}

View File

@ -41,7 +41,11 @@ struct MeloNXApp: App {
@AppStorage("autoJIT") var autoJIT = false
@State var fourgbiPad = false
@State var ios19 = 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)
var body: some Scene {
WindowGroup {
@ -77,10 +81,19 @@ struct MeloNXApp: App {
withAnimation(.easeOut) {
finishedStorage = newValue
}
if #available(iOS 19, *), newValue {
dualMapped = !ProcessInfo.processInfo.isiOSAppOnMac
dualMappededit = true
}
}
}
}
.onAppear() {
if #available(iOS 19, *), ProcessInfo.processInfo.hasTXM, !ignores19 {
ios19 = true
}
if UIDevice.current.userInterfaceIdiom == .pad && !ignores {
print((Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000))
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 }
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)]
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 0;
return coefficients[index];
}
return coefficients[index];
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
}
[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.Types;
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.Common;
using Ryujinx.Common.Logging;

View File

@ -2,23 +2,17 @@ using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Ryujinx.Cpu.LightningJit.Cache
{
class WriteZeroCache : IDisposable
class DualMappedNoWxCache : IDisposable
{
private const int CodeAlignment = 4;
private const int InitialCacheSize = 2 * 1024 * 1024;
private const int GrowthCacheSize = 2 * 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);
private const int CodeAlignment = 4; // Bytes.
private const int SharedCacheSize = 512 * 1024 * 1024;
private const int LocalCacheSize = 128 * 1024 * 1024;
// 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.
@ -26,104 +20,24 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private class MemoryCache : IDisposable
{
private readonly ReservedRegion _region;
private readonly DualMappedJitAllocator _allocator;
private readonly CacheMemoryAllocator _cacheAllocator;
public readonly IJitMemoryAllocator Allocator;
private readonly ulong _maxSize;
private ulong _currentSize;
private readonly Dictionary<int, HashSet<int>> _reusePages;
private readonly object _reuselock = new object();
public DualMappedJitAllocator Allocator => _allocator;
public IntPtr RwPointer => _allocator.RwPtr;
public IntPtr RxPointer => _allocator.RxPtr;
public CacheMemoryAllocator CacheAllocator => _cacheAllocator;
public IntPtr Pointer => _region.Block.Pointer;
public ulong CurrentSize => _currentSize;
public ulong MaxSize => _maxSize;
public IntPtr Pointer => _allocator.RwPtr;
public MemoryCache(IJitMemoryAllocator allocator, ulong maxSize)
public MemoryCache(ulong size)
{
Allocator = allocator;
_maxSize = maxSize;
_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);
}
_allocator = new DualMappedJitAllocator(size);
_cacheAllocator = new((int)size);
}
public int Allocate(int 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);
@ -132,63 +46,19 @@ namespace Ryujinx.Cpu.LightningJit.Cache
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;
}
public void Free(int offset, int size)
{
if (size >= (int)MemoryBlock.GetPageSize() && (size % (int)MemoryBlock.GetPageSize() == 0) &&
(offset % (int)MemoryBlock.GetPageSize() == 0))
{
AddReusablePage(offset, size);
}
else
{
_cacheAllocator.Free(offset, size);
}
_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())
{
JitSupportDarwin.SysIcacheInvalidate(_region.Block.Pointer + offset, size);
JitSupportDarwin.SysIcacheInvalidate(_allocator.RxPtr + offset, size);
}
else
{
@ -196,14 +66,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
}
public void ClearReusePool()
{
lock (_reuselock)
{
_reusePages.Clear();
}
}
private static int AlignCodeSize(int codeSize)
{
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
@ -213,8 +75,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{
if (disposing)
{
ClearReusePool();
_region.Dispose();
_allocator.Dispose();
_cacheAllocator.Clear();
}
}
@ -259,12 +120,12 @@ namespace Ryujinx.Cpu.LightningJit.Cache
[ThreadStatic]
private static Dictionary<ulong, ThreadLocalCacheEntry> _threadLocalCache;
public WriteZeroCache(IJitMemoryAllocator allocator, IStackWalker stackWalker, Translator translator)
public DualMappedNoWxCache(IJitMemoryAllocator allocator, IStackWalker stackWalker, Translator translator)
{
_stackWalker = stackWalker;
_translator = translator;
_sharedCaches = new List<MemoryCache> { new(allocator, MaxSharedCacheSize) };
_localCaches = new List<MemoryCache> { new(allocator, MaxLocalCacheSize) };
_sharedCaches = new List<MemoryCache> { new(SharedCacheSize) };
_localCaches = new List<MemoryCache> { new(LocalCacheSize) };
_pendingMaps = new Dictionary<ulong, PageAlignedRangeList>();
_lock = new();
}
@ -275,7 +136,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
if (!_pendingMaps.TryGetValue(cacheKey, out var pendingMap))
{
pendingMap = new PageAlignedRangeList(
(offset, size) => _sharedCaches[cacheIndex].ReprotectAsRx(offset, size),
(offset, size) => _sharedCaches[cacheIndex].SysIcacheInvalidate(offset, size),
(address, func) => RegisterFunction(address, func));
_pendingMaps[cacheKey] = pendingMap;
}
@ -304,15 +165,13 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
catch (OutOfMemoryException)
{
// Try next cache
}
}
// All existing caches are full, create a new one
lock (_lock)
{
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);
}
}
@ -327,14 +186,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
catch (OutOfMemoryException)
{
// Try next cache
}
}
lock (_lock)
{
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);
}
}
@ -360,8 +219,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
MemoryCache cache = _sharedCaches[cacheIndex];
funcPtr = cache.Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
funcPtr = cache.RxPointer + funcOffset;
TranslatedFunction function = new(funcPtr, guestSize);
@ -396,21 +255,22 @@ namespace Ryujinx.Cpu.LightningJit.Cache
Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0);
IntPtr funcPtr1 = _sharedCaches[cacheIndex].Pointer + funcOffset;
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;
}
catch (OutOfMemoryException)
{
// Try next cache
}
}
var allocator = _sharedCaches[0].Allocator;
var newCache = new MemoryCache(allocator, MaxSharedCacheSize);
var newCache = new MemoryCache(SharedCacheSize);
_sharedCaches.Add(newCache);
cacheIndex = _sharedCaches.Count - 1;
@ -425,8 +285,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
IntPtr funcPtr = newCache.Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
funcPtr = newCache.RxPointer + funcOffset;
newCache.ReprotectAsRx(funcOffset, newSizeAligned);
newCache.SysIcacheInvalidate(funcOffset, newSizeAligned);
return funcPtr;
}
@ -481,8 +342,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
for (int i = 0; i < _localCaches.Count; i++)
{
cachePointers[i] = _localCaches[i].Pointer;
cacheSizes[i] = (int)_localCaches[i].CurrentSize;
cachePointers[i] = _localCaches[i].RxPointer;
cacheSizes[i] = LocalCacheSize;
}
IntPtr[] sharedPointers = new IntPtr[_sharedCaches.Count];
@ -490,19 +351,20 @@ namespace Ryujinx.Cpu.LightningJit.Cache
for (int i = 0; i < _sharedCaches.Count; i++)
{
sharedPointers[i] = _sharedCaches[i].Pointer;
sharedSizes[i] = (int)_sharedCaches[i].CurrentSize;
sharedPointers[i] = _sharedCaches[i].RxPointer;
sharedSizes[i] = SharedCacheSize;
}
// Iterate over the arrays and pass each element to GetCallStack
IEnumerable<ulong> callStack = null;
for (int i = 0; i < _localCaches.Count; i++)
{
callStack = _stackWalker.GetCallStack(
framePointer,
cachePointers[i],
cacheSizes[i],
sharedPointers[i],
sharedSizes[i]
cachePointers[i], // Passing each individual cachePointer
cacheSizes[i], // Passing each individual cacheSize
sharedPointers[i], // Passing each individual sharedPointer
sharedSizes[i] // Passing each individual sharedSize
);
}
@ -510,12 +372,16 @@ namespace Ryujinx.Cpu.LightningJit.Cache
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);
if (!canDelete)
{
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)
{
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);
_localCaches[cacheIndex].Free(offset, sizeAligned);
_localCaches[cacheIndex].ReprotectAsRw(offset, sizeAligned);
}
}
public void ClearEntireThreadLocalCache()
{
// Thread is exiting, delete everything.
if (_threadLocalCache == null)
{
return;
@ -560,7 +428,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
var (cacheIndex, offset) = SplitCacheOffset(entry.Offset);
_localCaches[cacheIndex].Free(offset, sizeAligned);
_localCaches[cacheIndex].ReprotectAsRw(offset, sizeAligned);
}
_threadLocalCache.Clear();
@ -577,10 +444,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
IntPtr funcPtr = _localCaches[cacheIndex].Pointer + funcOffset;
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
funcPtr = _localCaches[cacheIndex].RxPointer + funcOffset;
(_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr, cacheIndex));
_localCaches[cacheIndex].ReprotectAsRx(funcOffset, alignedSize);
_localCaches[cacheIndex].SysIcacheInvalidate(funcOffset, alignedSize);
return funcPtr;
}

View File

@ -5,6 +5,7 @@ using System.Runtime.Versioning;
namespace Ryujinx.Cpu.LightningJit.Cache
{
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
static partial class JitSupportDarwin
{
[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 NoWxCache _noWxCache;
private readonly WriteZeroCache _writeZeroCache;
private readonly DualMappedNoWxCache _dualMappedCache;
private bool _disposed;
internal TranslatorCache<TranslatedFunction> Functions { get; }
@ -57,27 +57,14 @@ namespace Ryujinx.Cpu.LightningJit
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");
if (content.Contains("22E5200s") && content.Contains("18.4") && content.Contains("Beta"))
{
// 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.
// Apple has confirmed that this change will be coming to later iOS releases.
// 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);
}
Console.WriteLine($"Dual Mapped JIT enabled.");
_dualMappedCache = new(new JitMemoryAllocator(), CreateStackWalker(), this);
Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
Stubs = new TranslatorStubs(FunctionTable, _dualMappedCache);
}
else
{
@ -125,7 +112,7 @@ namespace Ryujinx.Cpu.LightningJit
NativeInterface.UnregisterThread();
_noWxCache?.ClearEntireThreadLocalCache();
_writeZeroCache?.ClearEntireThreadLocalCache();
_dualMappedCache?.ClearEntireThreadLocalCache();
}
internal IntPtr GetOrTranslatePointer(IntPtr framePointer, ulong address, ExecutionMode mode)
@ -135,10 +122,10 @@ namespace Ryujinx.Cpu.LightningJit
CompiledFunction func = Compile(address, mode);
return _noWxCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength);
}
else if (_writeZeroCache != null)
else if (_dualMappedCache != null)
{
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;
@ -239,9 +226,9 @@ namespace Ryujinx.Cpu.LightningJit
{
_noWxCache.Dispose();
}
else if (_writeZeroCache != null)
else if (_dualMappedCache != null)
{
_writeZeroCache.Dispose();
_dualMappedCache.Dispose();
}
else
{

View File

@ -25,7 +25,7 @@ namespace Ryujinx.Cpu.LightningJit
private readonly AddressTable<ulong> _functionTable;
private readonly NoWxCache _noWxCache;
private readonly WriteZeroCache _writeZeroCache;
private readonly DualMappedNoWxCache _dualMappedCache;
private readonly GetFunctionAddressDelegate _getFunctionAddressRef;
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="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>
public TranslatorStubs(AddressTable<ulong> functionTable, WriteZeroCache writeZeroCache)
public TranslatorStubs(AddressTable<ulong> functionTable, DualMappedNoWxCache dualMappedCache)
{
ArgumentNullException.ThrowIfNull(functionTable);
_functionTable = functionTable;
_writeZeroCache = writeZeroCache;
_dualMappedCache = dualMappedCache;
_getFunctionAddressRef = NativeInterface.GetFunctionAddress;
_getFunctionAddress = Marshal.GetFunctionPointerForDelegate(_getFunctionAddressRef);
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
@ -131,7 +131,7 @@ namespace Ryujinx.Cpu.LightningJit
{
if (!_disposed)
{
if (_noWxCache == null)
if (_noWxCache == null || _dualMappedCache == null)
{
if (_dispatchStub.IsValueCreated)
{
@ -383,9 +383,9 @@ namespace Ryujinx.Cpu.LightningJit
{
return _noWxCache.MapPageAligned(code);
}
else if (_writeZeroCache != null)
else if (_dualMappedCache != null)
{
return _writeZeroCache.MapPageAligned(code);
return _dualMappedCache.MapPageAligned(code);
}
else
{

View File

@ -127,11 +127,12 @@ namespace Ryujinx.Cpu.Signal
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.Reprotect(0, codeSizeAligned, MemoryPermission.ReadAndExecute);
return _codeBlock.Pointer;
return _codeBlock.RxPointer;
}
private static unsafe ref SignalHandlerConfig GetConfigRef()

View File

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

View File

@ -680,6 +680,8 @@ namespace Ryujinx.Graphics.Vulkan
ShaderCollection program = _program;
// UpdateAndBindTexturesWithoutTemplate jas been renamed to UpdateAndBind and supports more then just textures.
if (_dirty.HasFlag(DirtyFlags.Uniform))
{
if (program.UsePushDescriptors)
@ -688,51 +690,77 @@ namespace Ryujinx.Graphics.Vulkan
}
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))
{
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 (true)
if (program.UpdateTexturesWithoutTemplate)
{
try
{
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
catch (Exception e)
{
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
}
UpdateAndBind(cbs, PipelineBase.TextureSetIndex, pbp);
}
else
{
try
{
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
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))
{
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)
{
// 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;
@ -764,7 +792,7 @@ namespace Ryujinx.Graphics.Vulkan
if (info.Buffer.Handle == 0)
{
info.Buffer = dummyBuffer?.Get(cbs).Value ?? default;
// info.Offset = 0;
info.Offset = 0;
info.Range = Vk.WholeSize;
}
@ -962,9 +990,9 @@ namespace Ryujinx.Graphics.Vulkan
}
[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];
if (bindingSegments.Length == 0)
@ -972,6 +1000,24 @@ namespace Ryujinx.Graphics.Vulkan
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
{
ImageView = _dummyTexture.GetImageView().Get(cbs).Value,
@ -979,89 +1025,160 @@ namespace Ryujinx.Graphics.Vulkan
ImageLayout = ImageLayout.General
};
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
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 var textureRef = ref _textureRefs[index];
ref BufferRef buffer = ref _uniformBufferRefs[index];
var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler;
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
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,
Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler,
ImageLayout = ImageLayout.General
};
int index = binding + i;
ref var textureRef = ref _textureRefs[index];
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
{
for (int i = 0; i < count; i++)
var arrayRef = _textureArrayRefs[binding];
if (segment.Type != ResourceType.BufferTexture)
{
int index = binding + i;
var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default;
dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer);
var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler);
if (imageInfos != null)
{
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.BufferTexture)
if (segment.Type != ResourceType.BufferImage)
{
var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler);
if (imageInfos != null)
Span<DescriptorImageInfo> images = _images;
for (int i = 0; i < count; i++)
{
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);
}
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
}
dsc.UpdateImages(0, binding, images[..count], DescriptorType.StorageImage);
}
else
{
var bufferViews = arrayRef.Array.GetBufferViews(cbs);
if (bufferViews != null)
Span<BufferView> bufferImages = _bufferImages;
for (int i = 0; i < count; i++)
{
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);
}
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, true) ?? default;
}
dsc.UpdateBufferImages(0, binding, bufferImages[..count], DescriptorType.StorageTexelBuffer);
}
}
}
var sets = dsc.GetSets();
_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);
}
}
}
}

View File

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

View File

@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Vulkan
{
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;
@ -185,8 +185,8 @@ namespace Ryujinx.Graphics.Vulkan
if (gd.Capabilities.SupportsMultiView)
{
pipeline.ScissorsCount = Constants.MaxViewports;
pipeline.ViewportsCount = Constants.MaxViewports;
pipeline.ScissorsCount = (uint)Constants.MaxViewports;
pipeline.ViewportsCount = (uint)Constants.MaxViewports;
}
else
{

View File

@ -137,7 +137,7 @@ namespace Ryujinx.Graphics.Vulkan
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
// 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;
_firstBackgroundUse = false;

View File

@ -402,9 +402,7 @@ namespace Ryujinx.Graphics.Vulkan
properties.Limits.FramebufferDepthSampleCounts &
properties.Limits.FramebufferStencilSampleCounts;
bool isDynamicStateSupported = OperatingSystem.IsIOS()
? OperatingSystem.IsIOSVersionAtLeast(17) && _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName)
: _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName);
bool isDynamicStateSupported = OperatingSystem.IsIOSVersionAtLeast(17) && _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName);
Capabilities = new HardwareCapabilities(
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
@ -422,8 +420,8 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
isDynamicStateSupported,
features2.Features.MultiViewport && !IsMoltenVk, // Workaround for AMD on MoltenVK issue
!IsMoltenVk ? featuresRobustness2.NullDescriptor : false,
features2.Features.MultiViewport, // && !IsMoltenVk, // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor && !IsMoltenVk,
supportsPushDescriptors && !IsMoltenVk,
propertiesPushDescriptor.MaxPushDescriptors,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
@ -649,8 +647,7 @@ namespace Ryujinx.Graphics.Vulkan
if (!OperatingSystem.IsIOSVersionAtLeast(16, 4))
{
// On iOS 16.4 and later, these formats are supported.
// On earlier versions, it is not supported.
// On iOS 16.3.1 and earlier, these formats are not supported.
supportsBc123CompressionFormat = false;
supportsBc45CompressionFormat = false;
supportsBc67CompressionFormat = false;
@ -723,7 +720,7 @@ namespace Ryujinx.Graphics.Vulkan
SystemMemoryType memoryType;
if (IsSharedMemory && !IsMoltenVk)
if (IsSharedMemory)
{
memoryType = SystemMemoryType.UnifiedMemory;
}
@ -785,10 +782,10 @@ namespace Ryujinx.Graphics.Vulkan
imageSetIndex: PipelineBase.ImageSetIndex,
extraSetBaseIndex: PipelineBase.DescriptorSetLayouts,
maximumExtraSets: Math.Max(0, (int)limits.MaxBoundDescriptorSets - PipelineBase.DescriptorSetLayouts),
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
maximumImagesPerStage: Constants.MaxImagesPerStage,
maximumUniformBuffersPerStage: (uint)Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: (uint)Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: (uint)Constants.MaxTexturesPerStage,
maximumImagesPerStage: (uint)Constants.MaxImagesPerStage,
maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize,
maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy,
shaderSubgroupSize: (int)Capabilities.SubgroupSize,

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@ -48,7 +48,7 @@
<Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
<ItemGroup>
<!-- <LinkerArg Include="-Wl,-ld_classic" /> -->
<!-- <LinkerArg Include="-Wl,-ld_classic" /> -->
<LinkerArg Include="-flto -Ofast" />
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
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.
/// </summary>
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 _viewCompatible;
private readonly bool _forJit;
private DualMappedJitAllocator _dualMappedAllocator;
private IntPtr _sharedMemory;
private IntPtr _pointer;
private IntPtr _rxPointer;
/// <summary>
/// Pointer to the memory block data.
/// Pointer to the memory block data (RW).
/// </summary>
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>
/// Size of the memory block.
/// </summary>
@ -35,7 +42,16 @@ namespace Ryujinx.Memory
/// <exception cref="PlatformNotSupportedException">Throw when the current platform is not supported</exception>
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));
@ -58,7 +74,7 @@ namespace Ryujinx.Memory
_pointer = MemoryManagement.Allocate(size, _forJit);
}
Size = size;
_rxPointer = _pointer;
}
/// <summary>
@ -165,7 +181,10 @@ namespace Ryujinx.Memory
/// <exception cref="MemoryProtectionException">Throw when <paramref name="permission"/> is invalid</exception>
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>
@ -388,8 +407,13 @@ namespace Ryujinx.Memory
{
IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero);
// If pointer is null, the memory was already freed or never allocated.
if (ptr != IntPtr.Zero)
if (_dualMappedAllocator != null)
{
_dualMappedAllocator.Dispose();
_dualMappedAllocator = null;
_rxPointer = IntPtr.Zero;
}
else if (ptr != IntPtr.Zero)
{
if (_usesSharedMemory)
{