forked from MeloNX/MeloNX
Compare commits
24 Commits
drm-backgr
...
XC-ios-ht
Author | SHA1 | Date | |
---|---|---|---|
302ddeee85 | |||
|
1b69c0bdc6 | ||
|
18d98755f6 | ||
|
c6de4abce3 | ||
|
e5c5e8572e | ||
c0e8570293 | |||
c8a3124cca | |||
2c389c899a | |||
11571aca6e | |||
|
e04e689bc4 | ||
5c903626cc | |||
9ca187a8c4 | |||
cac3853d96 | |||
fff70a2dba | |||
4da30e332c | |||
|
114ba3eb57 | ||
|
839ddab589 | ||
|
00a06c4dc8 | ||
efbeebafcb | |||
|
b85758ba88 | ||
|
46196daf39 | ||
|
eb4a4593ea | ||
|
c3ade6f5cd | ||
|
007cb026a4 |
21
.gitea/workflows/add_release_to_site.yml
Normal file
21
.gitea/workflows/add_release_to_site.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Notify API on Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
notify-api:
|
||||||
|
runs-on: debian-trixie
|
||||||
|
steps:
|
||||||
|
- name: Send API Call for New Release
|
||||||
|
run: |
|
||||||
|
curl -X POST http://melonx.org/api/new_release \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer ${{ secrets.MELONX_GITEA_API_KEY }}" \
|
||||||
|
-d '{
|
||||||
|
"version_number": "${{ github.event.release.tag_name }}",
|
||||||
|
"download_link": "${{ github.event.release.html_url }}",
|
||||||
|
"changelog": "${{ github.event.release.body }}",
|
||||||
|
"is_latest": true
|
||||||
|
}'
|
79
Compile.md
79
Compile.md
@ -1,33 +1,66 @@
|
|||||||
# How to compile MeloNX using macOS
|
# Compiling MeloNX on macOS
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
- [.NET 8.0](<https://dotnet.microsoft.com/en-us/download/dotnet/8.0>)
|
|
||||||
- A computer with macOS
|
|
||||||
|
|
||||||
## Compiling
|
Before you begin, ensure you have the following installed:
|
||||||
1. Clone the Git Repo and build Ryujinx
|
|
||||||
```
|
|
||||||
git clone https://github.com/melonx-emu/melonx/tree/XC-ios-ht
|
|
||||||
cd melonx
|
|
||||||
./compile.sh -x
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Open the Xcode project, stored at MeloNX/src/MeloNX
|
- [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||||
|
- A Mac running **macOS**
|
||||||
|
|
||||||
3. Make sure `Ryujinx.SDL2.Headless.dylib` is set to `Embed & Sign` in the General settings for the Xcode project
|
## Compilation Steps
|
||||||
|
|
||||||
4. Signing & Capabilities
|
|
||||||
Change your 'Team' to your Developer Account (free or paid) and change Bundle Identifier to
|
|
||||||
`com.*your name*.MeloNX`
|
|
||||||
|
|
||||||
6. Build and Run
|
### 1. Clone the Repository and Build Ryujinx
|
||||||
`CMD+R`
|
|
||||||
|
|
||||||
7. Check the [post-setup guide](<https://github.com/melonx-emu/melonx/tree/XC-ios-ht/postsetup.md>)
|
Open a terminal and run:
|
||||||
|
|
||||||
## If you don't have a paid developer account
|
```sh
|
||||||
Make sure these entitlements are removed if you don't have a paid Apple Developer account
|
git clone https://git.743378673.xyz/MeloNX/MeloNX.git
|
||||||
|
cd MeloNX
|
||||||
|
./compile.sh
|
||||||
```
|
```
|
||||||
Extended Virtual Addressing
|
|
||||||
Increased Debugging Memory Limit
|
You may need to run this command if compilation fails, then run the `./compile.sh` command again (You will need to put in your user password. Your password will not be shown at all.)
|
||||||
```
|
```
|
||||||
|
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Open the Xcode Project
|
||||||
|
|
||||||
|
Navigate to the **Xcode project file** located at:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/MeloNX/MeloNX.xcodeproj
|
||||||
|
```
|
||||||
|
|
||||||
|
Double-click to open it in **Xcode**.
|
||||||
|
|
||||||
|
### 3. Configure the Project Settings
|
||||||
|
|
||||||
|
- In **Xcode**, select the **MeloNX** project.
|
||||||
|
- Under the **General** tab, find `Ryujinx.Headless.SDL2.dylib`.
|
||||||
|
- Set its **Embed setting** to **"Embed & Sign"**.
|
||||||
|
|
||||||
|
### 4. Configure Signing & Capabilities
|
||||||
|
|
||||||
|
- In **Xcode**, go to **Signing & Capabilities**.
|
||||||
|
- Set the **Team** to your **Apple Developer account** (free or paid).
|
||||||
|
- Change the **Bundle Identifier** to:
|
||||||
|
|
||||||
|
```
|
||||||
|
com.<your-name>.MeloNX
|
||||||
|
```
|
||||||
|
|
||||||
|
*(Replace `<your-name>` with your actual name or identifier.)*
|
||||||
|
|
||||||
|
### 5. Connect Your Device
|
||||||
|
|
||||||
|
Ensure your **iPhone/iPad** is **connected** and **recognized** in Xcode.
|
||||||
|
|
||||||
|
### 6. Build and Run
|
||||||
|
|
||||||
|
Click the **Run (▶️) button** in Xcode to compile and launch MeloNX.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Now you're all set! 🚀 If you encounter issues, please join the discord at https://melonx.org
|
||||||
|
```
|
111
README.md
111
README.md
@ -1,9 +1,13 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/MeloNX-Emu/MeloNX">
|
<a href="https://melonx.org">
|
||||||
<img src="https://github.com/MeloNX-Emu/melonx-emu.github.io/blob/main/favicon.png?raw=true" alt="MeloNX Logo" width="120">
|
<img src="https://melonx.org/static/imgs/MeloNX.svg" alt="MeloNX Logo" width="120">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">MeloNX</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
MeloNX enables Nintendo Switch game emulation on iOS using the Ryujinx iOS code base.
|
MeloNX enables Nintendo Switch game emulation on iOS using the Ryujinx iOS code base.
|
||||||
</p>
|
</p>
|
||||||
@ -13,10 +17,105 @@
|
|||||||
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">MIT license</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">MIT license</a>. <br
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Compatibility
|
# Compatibility
|
||||||
|
|
||||||
MeloNX works on iPhone X and later and iPad 7th Gen and later. A lot of games work.
|
MeloNX works on iPhone X and later and iPad 7th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/$0" target="_blank">website</a>.
|
||||||
|
|
||||||
## Usage
|
# Usage
|
||||||
|
|
||||||
To run MeloNX on your iOS device, at least 4GB of RAM is recommended to ensure stability. For full instructions, refer to our [Setup Guide](https://github.com/MeloNX-Emu/MeloNX/wiki/Setup-Guide).
|
## FAQ
|
||||||
|
- MeloNX is made for iOS 17+, iOS 15 - 16 is supported but will have issues.
|
||||||
|
- MeloNX needs Xcode or a Paid Apple Developer Account. SideStore support may come soon (SideStore Side Issue)
|
||||||
|
- MeloNX needs JIT
|
||||||
|
- Recommended Device: iPhone 15 Pro or newer.
|
||||||
|
- Low-End Recommended Device**: iPhone 13 Pro.
|
||||||
|
- Lowest Supported Device: iPhone XR
|
||||||
|
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
### Paid Developer Account
|
||||||
|
|
||||||
|
1. **Sideload the App**
|
||||||
|
- Use any sideloading tool that supports Apple IDs.
|
||||||
|
|
||||||
|
2. **Enable Entitlements**
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
4. **Enable JIT**
|
||||||
|
- Use your preferred method to enable Just-In-Time (JIT) compilation.
|
||||||
|
|
||||||
|
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**.
|
||||||
|
- Copy the **bis** and **system** folders
|
||||||
|
|
||||||
|
### Xcode
|
||||||
|
|
||||||
|
1. **Compile Guide**
|
||||||
|
- Visit the [guide here](https://git.743378673.xyz/MeloNX/MeloNX/src/branch/XC-ios-ht/Compile.md).
|
||||||
|
|
||||||
|
2. **Add Necessary Files**
|
||||||
|
|
||||||
|
If having Issues installing firmware (Make sure your Keys are installed first)
|
||||||
|
- If needed, install firmware and keys from **Ryujinx Desktop**.
|
||||||
|
- Copy the **bis** and **system** folders
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Audio**
|
||||||
|
|
||||||
|
Audio output is entirely supported, audio input (microphone) isn't supported.
|
||||||
|
We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
|
||||||
|
|
||||||
|
- **CPU**
|
||||||
|
|
||||||
|
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support.
|
||||||
|
It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
|
||||||
|
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster).
|
||||||
|
The fastest option (host, unchecked) is set by default.
|
||||||
|
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads.
|
||||||
|
The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game.
|
||||||
|
NOTE: This feature is enabled by default, You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch!
|
||||||
|
These improvements are permanent and do not require any extra launches going forward.
|
||||||
|
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
- **Input**
|
||||||
|
|
||||||
|
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers.
|
||||||
|
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
|
||||||
|
In all scenarios, you can set up everything inside the input configuration menu.
|
||||||
|
|
||||||
|
- **DLC & Modifications**
|
||||||
|
|
||||||
|
MeloNX does not support add-on content/downloadable content.
|
||||||
|
Mods (romfs, exefs, and runtime mods such as cheats) are supported;
|
||||||
|
|
||||||
|
- **Configuration**
|
||||||
|
|
||||||
|
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This software is licensed under the terms of the [MIT 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
|
||||||
|
|
||||||
|
- [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.
|
||||||
|
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||||
|
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||||
|
@ -54,6 +54,16 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
4E50F49E2D5CC28B0080F1D1 /* Embed Watch Content */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||||
|
dstSubfolderSpec = 16;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Watch Content";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
4E80AA092CD6FAA800029585 /* Embed Libraries */ = {
|
4E80AA092CD6FAA800029585 /* Embed Libraries */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -67,6 +77,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
4E7023A52D5A98E2002C7183 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||||
4E80A98D2CD6F54500029585 /* MeloNX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeloNX.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A98D2CD6F54500029585 /* MeloNX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeloNX.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -96,7 +107,7 @@
|
|||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
|
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
|
||||||
CodeSignOnCopy,
|
CodeSignOnCopy,
|
||||||
);
|
);
|
||||||
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework" = (
|
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework" = (
|
||||||
CodeSignOnCopy,
|
CodeSignOnCopy,
|
||||||
RemoveHeadersOnCopy,
|
RemoveHeadersOnCopy,
|
||||||
);
|
);
|
||||||
@ -157,7 +168,7 @@
|
|||||||
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
||||||
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework",
|
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework",
|
||||||
Dependencies/XCFrameworks/libavcodec.xcframework,
|
Dependencies/XCFrameworks/libavcodec.xcframework,
|
||||||
Dependencies/XCFrameworks/libavfilter.xcframework,
|
Dependencies/XCFrameworks/libavfilter.xcframework,
|
||||||
Dependencies/XCFrameworks/libavformat.xcframework,
|
Dependencies/XCFrameworks/libavformat.xcframework,
|
||||||
@ -232,6 +243,7 @@
|
|||||||
4E80AA192CD700F500029585 /* Frameworks */ = {
|
4E80AA192CD700F500029585 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4E7023A52D5A98E2002C7183 /* UIKit.framework */,
|
||||||
4E80AA622CD7122800029585 /* GameController.framework */,
|
4E80AA622CD7122800029585 /* GameController.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
@ -267,6 +279,7 @@
|
|||||||
4E80A98A2CD6F54500029585 /* Frameworks */,
|
4E80A98A2CD6F54500029585 /* Frameworks */,
|
||||||
4E80A98B2CD6F54500029585 /* Resources */,
|
4E80A98B2CD6F54500029585 /* Resources */,
|
||||||
4E80AA092CD6FAA800029585 /* Embed Libraries */,
|
4E80AA092CD6FAA800029585 /* Embed Libraries */,
|
||||||
|
4E50F49E2D5CC28B0080F1D1 /* Embed Watch Content */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -337,7 +350,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1610;
|
LastSwiftUpdateCheck = 1620;
|
||||||
LastUpgradeCheck = 1610;
|
LastUpgradeCheck = 1610;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
4E80A98C2CD6F54500029585 = {
|
4E80A98C2CD6F54500029585 = {
|
||||||
@ -634,6 +647,15 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -646,8 +668,7 @@
|
|||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -677,8 +698,20 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.0.8;
|
MARKETING_VERSION = 1.1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@ -713,6 +746,15 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -725,8 +767,7 @@
|
|||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@ -756,8 +797,20 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.0.8;
|
MARKETING_VERSION = 1.1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
Binary file not shown.
@ -12,12 +12,12 @@
|
|||||||
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>2</integer>
|
<integer>3</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>1</integer>
|
<integer>4</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
// Created by Stossy11 on 3/11/2024.
|
// Created by Stossy11 on 3/11/2024.
|
||||||
//
|
//
|
||||||
|
|
||||||
#define DRM 1
|
#define DRM 0
|
||||||
|
#define CS_DEBUGGED 0x10000000
|
||||||
|
|
||||||
#ifndef RyujinxHeader
|
#ifndef RyujinxHeader
|
||||||
#define RyujinxHeader
|
#define RyujinxHeader
|
||||||
|
19
src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift
Normal file
19
src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// IsJITEnabled.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 10/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func isJITEnabled() -> Bool {
|
||||||
|
var flags: Int = 0
|
||||||
|
|
||||||
|
csops(getpid(), 0, &flags, sizeof(flags))
|
||||||
|
return (Int32(flags) & CS_DEBUGGED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func sizeof<T>(_ value: T) -> Int {
|
||||||
|
return MemoryLayout<T>.size
|
||||||
|
}
|
145
src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift
Normal file
145
src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
//
|
||||||
|
// EnableJIT.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 10/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// MARK: - Server Address Management
|
||||||
|
|
||||||
|
/// Key for storing the JITEB server address in UserDefaults
|
||||||
|
private let JITEB_SERVER_ADDRESS_KEY = "jitServerAddress"
|
||||||
|
|
||||||
|
/// Returns the current JITEB server address.
|
||||||
|
/// Defaults to `[fd00::]` if no address is set.
|
||||||
|
func getJITServerAddress() -> String {
|
||||||
|
return UserDefaults.standard.string(forKey: JITEB_SERVER_ADDRESS_KEY) ?? "[fd00::]"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the JITEB server address in UserDefaults.
|
||||||
|
func setJITServerAddress(_ address: String) {
|
||||||
|
UserDefaults.standard.set(address, forKey: JITEB_SERVER_ADDRESS_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - JIT Enablement
|
||||||
|
|
||||||
|
/// Enables JIT execution by communicating with the JITEB server.
|
||||||
|
func enableJITEB() {
|
||||||
|
guard let bundleID = Bundle.main.bundleIdentifier else {
|
||||||
|
print("Error: Unable to retrieve bundle ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the URL using the current server address
|
||||||
|
let serverAddress = getJITServerAddress()
|
||||||
|
let urlString = "http://\(serverAddress):9172/launch_app/\(bundleID)"
|
||||||
|
|
||||||
|
guard let address = URL(string: urlString) else {
|
||||||
|
print("Error: Invalid server address.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a network request to the JITEB server
|
||||||
|
let task = URLSession.shared.dataTask(with: address) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
print("Network error: \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
print("Error: Invalid server response.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = data else {
|
||||||
|
print("Error: No data received from server.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the launch status to the user
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let rootViewController = UIApplication.shared.windows.last?.rootViewController {
|
||||||
|
showLaunchAppAlert(jsonData: data, in: rootViewController)
|
||||||
|
} else {
|
||||||
|
print("Error: Unable to access root view controller.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Launch App Response Handling
|
||||||
|
|
||||||
|
/// Struct to decode the JSON response from the JITEB server
|
||||||
|
struct LaunchApp: Codable {
|
||||||
|
let ok: Bool
|
||||||
|
let error: String?
|
||||||
|
let launching: Bool
|
||||||
|
let position: Int?
|
||||||
|
let mounting: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an alert with the app launch status.
|
||||||
|
func showLaunchAppAlert(jsonData: Data, in viewController: UIViewController) {
|
||||||
|
do {
|
||||||
|
let result = try JSONDecoder().decode(LaunchApp.self, from: jsonData)
|
||||||
|
|
||||||
|
var message = ""
|
||||||
|
|
||||||
|
if let error = result.error {
|
||||||
|
message = "Error: \(error)"
|
||||||
|
} else if result.mounting {
|
||||||
|
message = "App is mounting..."
|
||||||
|
} else if result.launching {
|
||||||
|
message = "App is launching..."
|
||||||
|
} else {
|
||||||
|
message = "App launch status unknown."
|
||||||
|
}
|
||||||
|
|
||||||
|
if let position = result.position {
|
||||||
|
message += "\nPosition: \(position)"
|
||||||
|
}
|
||||||
|
|
||||||
|
let alert = UIAlertController(title: "Launch Status", message: message, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
viewController.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
let alert = UIAlertController(title: "Decoding Error", message: error.localizedDescription, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
viewController.present(alert, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Server Address Configuration
|
||||||
|
|
||||||
|
/// Displays an alert to allow the user to change the JITEB server address.
|
||||||
|
func configureJITServerAddress(in viewController: UIViewController) {
|
||||||
|
let alert = UIAlertController(title: "Configure JITEB Server", message: "Enter the server address (e.g., [fd00::] or 192.168.1.100):", preferredStyle: .alert)
|
||||||
|
|
||||||
|
alert.addTextField { textField in
|
||||||
|
textField.placeholder = "Server Address"
|
||||||
|
textField.text = getJITServerAddress()
|
||||||
|
}
|
||||||
|
|
||||||
|
alert.addAction(UIAlertAction(title: "Save", style: .default) { _ in
|
||||||
|
if let textField = alert.textFields?.first, let newAddress = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
|
||||||
|
setJITServerAddress(newAddress)
|
||||||
|
print("JITEB server address updated to: \(newAddress)")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
|
||||||
|
viewController.present(alert, animated: true)
|
||||||
|
}
|
@ -60,15 +60,6 @@ void ShowAlert(NSString* title, NSString* message, _Bool* showok)
|
|||||||
|
|
||||||
__attribute__((constructor)) static void entry(int argc, char **argv)
|
__attribute__((constructor)) static void entry(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (isJITEnabled()) {
|
|
||||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
||||||
[defaults setBool:YES forKey:@"JIT"];
|
|
||||||
[defaults synchronize]; // Ensure the value is saved immediately
|
|
||||||
} else {
|
|
||||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
||||||
[defaults setBool:NO forKey:@"JIT"];
|
|
||||||
[defaults synchronize]; // Ensure the value is saved immediately
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
|
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
|
||||||
NSLog(@"Entitlement Does Exist");
|
NSLog(@"Entitlement Does Exist");
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import GameController
|
import GameController
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -16,12 +17,79 @@ extension UIWindow {
|
|||||||
// Makes the SDLWindow use the current WindowScene instead of making its own window.
|
// Makes the SDLWindow use the current WindowScene instead of making its own window.
|
||||||
// Also waits for the window to append the on-screen controller
|
// Also waits for the window to append the on-screen controller
|
||||||
@objc func wdb_makeKeyAndVisible() {
|
@objc func wdb_makeKeyAndVisible() {
|
||||||
if #available(iOS 13.0, *) {
|
let enabled = UserDefaults.standard.bool(forKey: "oldWindowCode")
|
||||||
// self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
|
||||||
|
if #unavailable(iOS 17.0), enabled {
|
||||||
|
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wdb_makeKeyAndVisible()
|
self.wdb_makeKeyAndVisible()
|
||||||
theWindow = self
|
theWindow = self
|
||||||
Ryujinx.shared.repeatuntilfindLayer()
|
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
Ryujinx.shared.repeatuntilfindLayer()
|
||||||
|
} else if UserDefaults.standard.bool(forKey: "isVirtualController") && enabled {
|
||||||
|
waitForController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - iOS 16 and below Only
|
||||||
|
|
||||||
|
var hostingController: UIHostingController<ControllerView>?
|
||||||
|
func waitForController() {
|
||||||
|
guard let window = theWindow else { return }
|
||||||
|
|
||||||
|
// Function to search for an existing UIHostingController with ControllerView
|
||||||
|
func findGCControllerView(in view: UIView) -> UIHostingController<ControllerView>? {
|
||||||
|
if let hostingVC = view.next as? UIHostingController<ControllerView> {
|
||||||
|
return hostingVC
|
||||||
|
}
|
||||||
|
|
||||||
|
for subview in view.subviews {
|
||||||
|
if let found = findGCControllerView(in: subview) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let controllerView = ControllerView()
|
||||||
|
let newHostingController = UIHostingController(rootView: controllerView)
|
||||||
|
|
||||||
|
hostingController = newHostingController
|
||||||
|
|
||||||
|
let containerView = newHostingController.view!
|
||||||
|
containerView.backgroundColor = .clear
|
||||||
|
containerView.frame = window.bounds
|
||||||
|
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
|
|
||||||
|
// Timer for controller
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||||
|
if findGCControllerView(in: window) == nil {
|
||||||
|
// Adds Virtual Controller Subview
|
||||||
|
window.addSubview(containerView)
|
||||||
|
window.bringSubviewToFront(containerView)
|
||||||
|
|
||||||
|
if let sdlWindow = SDL_GetWindowFromID(1) {
|
||||||
|
SDL_SetWindowPosition(sdlWindow, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TransparentHostingContainerView: UIView {
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
// Check if the point is within the subviews of this container
|
||||||
|
let view = super.hitTest(point, with: event)
|
||||||
|
print(view)
|
||||||
|
|
||||||
|
// Return nil if the touch is outside visible content (passes through to views below)
|
||||||
|
return view === self ? nil : view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ class Ryujinx {
|
|||||||
@Published var metalLayer: CAMetalLayer? = nil
|
@Published var metalLayer: CAMetalLayer? = nil
|
||||||
@Published var firmwareversion = "0"
|
@Published var firmwareversion = "0"
|
||||||
@Published var emulationUIView = UIView()
|
@Published var emulationUIView = UIView()
|
||||||
|
@Published var games: [Game] = []
|
||||||
|
|
||||||
var shouldMetal: Bool {
|
var shouldMetal: Bool {
|
||||||
metalLayer == nil
|
metalLayer == nil
|
||||||
@ -65,7 +66,9 @@ class Ryujinx {
|
|||||||
|
|
||||||
static let shared = Ryujinx()
|
static let shared = Ryujinx()
|
||||||
|
|
||||||
private init() {}
|
private init() {
|
||||||
|
self.games = loadGames()
|
||||||
|
}
|
||||||
|
|
||||||
public struct Configuration : Codable, Equatable {
|
public struct Configuration : Codable, Equatable {
|
||||||
var gamepath: String
|
var gamepath: String
|
||||||
@ -88,6 +91,8 @@ class Ryujinx {
|
|||||||
var ignoreMissingServices: Bool
|
var ignoreMissingServices: Bool
|
||||||
var expandRam: Bool
|
var expandRam: Bool
|
||||||
var dfsIntegrityChecks: Bool
|
var dfsIntegrityChecks: Bool
|
||||||
|
var disablePTC: Bool
|
||||||
|
var disablevsync: Bool
|
||||||
|
|
||||||
|
|
||||||
init(gamepath: String,
|
init(gamepath: String,
|
||||||
@ -109,7 +114,9 @@ class Ryujinx {
|
|||||||
ignoreMissingServices: Bool = false,
|
ignoreMissingServices: Bool = false,
|
||||||
hypervisor: Bool = false,
|
hypervisor: Bool = false,
|
||||||
expandRam: Bool = false,
|
expandRam: Bool = false,
|
||||||
dfsIntegrityChecks: Bool = false
|
dfsIntegrityChecks: Bool = false,
|
||||||
|
disablePTC: Bool = false,
|
||||||
|
disablevsync: Bool = false
|
||||||
) {
|
) {
|
||||||
self.gamepath = gamepath
|
self.gamepath = gamepath
|
||||||
self.inputids = inputids
|
self.inputids = inputids
|
||||||
@ -131,6 +138,8 @@ class Ryujinx {
|
|||||||
self.ignoreMissingServices = ignoreMissingServices
|
self.ignoreMissingServices = ignoreMissingServices
|
||||||
self.hypervisor = hypervisor
|
self.hypervisor = hypervisor
|
||||||
self.dfsIntegrityChecks = dfsIntegrityChecks
|
self.dfsIntegrityChecks = dfsIntegrityChecks
|
||||||
|
self.disablePTC = disablePTC
|
||||||
|
self.disablevsync = disablevsync
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +151,7 @@ class Ryujinx {
|
|||||||
|
|
||||||
isRunning = true
|
isRunning = true
|
||||||
|
|
||||||
MainThread {
|
RunLoop.current.perform {
|
||||||
|
|
||||||
let url = URL(string: config.gamepath)
|
let url = URL(string: config.gamepath)
|
||||||
|
|
||||||
@ -187,20 +196,51 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func MainThread(_ block: @escaping @Sendable () -> Void) {
|
func loadGames() -> [Game] {
|
||||||
if #available(iOS 17.0, *) {
|
let fileManager = FileManager.default
|
||||||
RunLoop.current.perform {
|
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return [] }
|
||||||
autoreleasepool {
|
|
||||||
block()
|
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
||||||
}
|
|
||||||
}
|
if (!fileManager.fileExists(atPath: romsDirectory.path)) {
|
||||||
} else {
|
do {
|
||||||
DispatchQueue.main.async {
|
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
autoreleasepool {
|
} catch {
|
||||||
block()
|
print("Failed to create roms directory: \(error)")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var games: [Game] = []
|
||||||
|
|
||||||
|
do {
|
||||||
|
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
|
||||||
|
|
||||||
|
for fileURLCandidate in files {
|
||||||
|
if fileURLCandidate.pathExtension == "zip" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
|
||||||
|
let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
|
||||||
|
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
|
||||||
|
|
||||||
|
|
||||||
|
let gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
|
||||||
|
|
||||||
|
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: fileURLCandidate)
|
||||||
|
|
||||||
|
games.append(game)
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return games
|
||||||
|
} catch {
|
||||||
|
print("Error loading games from roms folder: \(error)")
|
||||||
|
return games
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildCommandLineArgs(from config: Configuration) -> [String] {
|
private func buildCommandLineArgs(from config: Configuration) -> [String] {
|
||||||
@ -227,8 +267,14 @@ class Ryujinx {
|
|||||||
args.append("--correct-controller")
|
args.append("--correct-controller")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.disablePTC {
|
||||||
|
args.append("--disable-ptc")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.disablevsync {
|
||||||
|
args.append("--disable-vsync")
|
||||||
|
}
|
||||||
|
|
||||||
// args.append("--disable-vsync")
|
|
||||||
|
|
||||||
if config.hypervisor {
|
if config.hypervisor {
|
||||||
args.append("--use-hypervisor")
|
args.append("--use-hypervisor")
|
||||||
@ -478,4 +524,3 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
86
src/MeloNX/MeloNX/App/Intents/LaunchGameIntent.swift
Normal file
86
src/MeloNX/MeloNX/App/Intents/LaunchGameIntent.swift
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// LaunchGameIntentDef.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 10/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
import Intents
|
||||||
|
import AppIntents
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
struct LaunchGameIntentDef: AppIntent {
|
||||||
|
|
||||||
|
static let title: LocalizedStringResource = "Launch Game"
|
||||||
|
|
||||||
|
static var description = IntentDescription("Launches the Selected Game.")
|
||||||
|
|
||||||
|
@Parameter(title: "Game", optionsProvider: GameOptionsProvider())
|
||||||
|
var gameName: String
|
||||||
|
|
||||||
|
static var parameterSummary: some ParameterSummary {
|
||||||
|
Summary("Launch \(\.$gameName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var openAppWhenRun: Bool = true
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func perform() async throws -> some IntentResult {
|
||||||
|
|
||||||
|
let ryujinx = Ryujinx.shared.games
|
||||||
|
|
||||||
|
let name = findClosestGameName(input: gameName, games: ryujinx.flatMap(\.titleName))
|
||||||
|
|
||||||
|
let urlString = "melonx://game?name=\(name ?? gameName)"
|
||||||
|
print(urlString)
|
||||||
|
if let url = URL(string: urlString) {
|
||||||
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .result()
|
||||||
|
}
|
||||||
|
|
||||||
|
func levenshteinDistance(_ a: String, _ b: String) -> Int {
|
||||||
|
let aCount = a.count
|
||||||
|
let bCount = b.count
|
||||||
|
var matrix = [[Int]](repeating: [Int](repeating: 0, count: bCount + 1), count: aCount + 1)
|
||||||
|
|
||||||
|
for i in 0...aCount {
|
||||||
|
matrix[i][0] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
for j in 0...bCount {
|
||||||
|
matrix[0][j] = j
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 1...aCount {
|
||||||
|
for j in 1...bCount {
|
||||||
|
let cost = a[a.index(a.startIndex, offsetBy: i - 1)] == b[b.index(b.startIndex, offsetBy: j - 1)] ? 0 : 1
|
||||||
|
matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix[aCount][bCount]
|
||||||
|
}
|
||||||
|
|
||||||
|
func findClosestGameName(input: String, games: [String]) -> String? {
|
||||||
|
let closestGame = games.min { a, b in
|
||||||
|
let distanceA = levenshteinDistance(input, a)
|
||||||
|
let distanceB = levenshteinDistance(input, b)
|
||||||
|
return distanceA < distanceB
|
||||||
|
}
|
||||||
|
return closestGame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
struct GameOptionsProvider: DynamicOptionsProvider {
|
||||||
|
func results() async throws -> [String] {
|
||||||
|
let dynamicGames = Ryujinx.shared.loadGames()
|
||||||
|
|
||||||
|
return dynamicGames.map { $0.titleName }
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
public struct Game: Identifiable, Equatable {
|
public struct Game: Identifiable, Equatable, Hashable {
|
||||||
public var id = UUID()
|
public var id = UUID()
|
||||||
|
|
||||||
var containerFolder: URL
|
var containerFolder: URL
|
||||||
|
@ -35,13 +35,14 @@ struct ContentView: View {
|
|||||||
@AppStorage("useTrollStore") var useTrollStore: Bool = false
|
@AppStorage("useTrollStore") var useTrollStore: Bool = false
|
||||||
|
|
||||||
// JIT
|
// JIT
|
||||||
@AppStorage("JIT") var isJITEnabled: Bool = false
|
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
|
||||||
|
|
||||||
// Other Configuration
|
// Other Configuration
|
||||||
@State var isMK8: Bool = false
|
@State var isMK8: Bool = false
|
||||||
@AppStorage("quit") var quit: Bool = false
|
@AppStorage("quit") var quit: Bool = false
|
||||||
@State var quits: Bool = false
|
@State var quits: Bool = false
|
||||||
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
||||||
|
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
|
||||||
|
|
||||||
// Loading Animation
|
// Loading Animation
|
||||||
@State private var clumpOffset: CGFloat = -100
|
@State private var clumpOffset: CGFloat = -100
|
||||||
@ -61,8 +62,9 @@ struct ContentView: View {
|
|||||||
// Metal Private API isn't needed and causes more stutters
|
// Metal Private API isn't needed and causes more stutters
|
||||||
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_DEBUG", value: "1"),
|
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "2"),
|
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "0"),
|
||||||
// MVK_CONFIG_LOG_LEVEL
|
// MVK_CONFIG_LOG_LEVEL
|
||||||
//MVK_DEBUG
|
//MVK_DEBUG
|
||||||
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64 or 192 depending on what i decide)
|
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64 or 192 depending on what i decide)
|
||||||
@ -71,7 +73,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
_settings = State(initialValue: defaultSettings)
|
_settings = State(initialValue: defaultSettings)
|
||||||
|
|
||||||
print("JIT Enabled: \(isJITEnabled)")
|
print("JIT Enabled: \(isJITEnabled())")
|
||||||
|
|
||||||
initializeSDL()
|
initializeSDL()
|
||||||
}
|
}
|
||||||
@ -86,38 +88,58 @@ struct ContentView: View {
|
|||||||
Air.play(AnyView(emulationView))
|
Air.play(AnyView(emulationView))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
ZStack {
|
||||||
emulationView
|
emulationView
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
|
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
|
||||||
/*
|
/*
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||||
timer.invalidate()
|
timer.invalidate()
|
||||||
quits = quit
|
quits = quit
|
||||||
|
|
||||||
if quits {
|
if quits {
|
||||||
quit = false
|
quit = false
|
||||||
timer.invalidate()
|
timer.invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is when the game starts to stop the animation
|
// This is when the game starts to stop the animation
|
||||||
EmulationView()
|
if #available(iOS 16, *) {
|
||||||
.onAppear() {
|
EmulationView()
|
||||||
isAnimating = false
|
.persistentSystemOverlays(.hidden)
|
||||||
|
.onAppear() {
|
||||||
|
isAnimating = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
VStack {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is the main menu view that includes the Settings and the Game Selector
|
// This is the main menu view that includes the Settings and the Game Selector
|
||||||
mainMenuView
|
mainMenuView
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
quits = false
|
quits = false
|
||||||
|
|
||||||
initControllerObservers() // This initializes the Controller Observers that refreshes the controller list when a new controller connecvts.
|
initControllerObservers() // This initializes the Controller Observers that refreshes the controller list when a new controller connecvts.
|
||||||
}
|
}
|
||||||
|
.onOpenURL() { url in
|
||||||
|
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||||
|
components.host == "game" {
|
||||||
|
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
||||||
|
|
||||||
|
game = Ryujinx.shared.games.first(where: { $0.titleId == text })
|
||||||
|
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
||||||
|
game = Ryujinx.shared.games.first(where: { $0.titleName == text })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -203,6 +225,7 @@ struct ContentView: View {
|
|||||||
withAnimation {
|
withAnimation {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
isAnimating = false
|
isAnimating = false
|
||||||
timer.invalidate()
|
timer.invalidate()
|
||||||
}
|
}
|
||||||
@ -232,17 +255,28 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Air.play(AnyView(
|
Air.play(AnyView(
|
||||||
Text("Select Game")
|
VStack {
|
||||||
.font(.system(size: 100))
|
Image(systemName: "gamecontroller")
|
||||||
|
.font(.system(size: 300))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
Text("Select Game")
|
||||||
|
.font(.system(size: 150))
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
|
let isJIT = isJITEnabled()
|
||||||
|
|
||||||
if !isJIT, useTrollStore {
|
if !isJIT, useTrollStore {
|
||||||
askForJIT()
|
askForJIT()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isJIT, jitStreamerEB {
|
||||||
|
enableJITEB()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,6 +356,11 @@ struct ContentView: View {
|
|||||||
setenv(setting.string, setting.value, 1)
|
setenv(setting.string, setting.value, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if syncqsubmits {
|
||||||
|
let setting = MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "2")
|
||||||
|
setenv(setting.string, setting.value, 1)
|
||||||
|
}
|
||||||
|
|
||||||
if config.inputids.isEmpty {
|
if config.inputids.isEmpty {
|
||||||
config.inputids.append("0")
|
config.inputids.append("0")
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ struct ControllerView: View {
|
|||||||
DPadView()
|
DPadView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
Spacer()
|
||||||
VStack {
|
VStack {
|
||||||
ShoulderButtonsViewRight()
|
ShoulderButtonsViewRight()
|
||||||
ZStack {
|
ZStack {
|
||||||
@ -53,7 +53,6 @@ struct ControllerView: View {
|
|||||||
ABXYView()
|
ABXYView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
@ -63,8 +62,8 @@ struct ControllerView: View {
|
|||||||
.padding(.horizontal, 40)
|
.padding(.horizontal, 40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, geometry.size.height / 3.2) // very broken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// could be landscape
|
// could be landscape
|
||||||
VStack {
|
VStack {
|
||||||
@ -100,12 +99,12 @@ struct ControllerView: View {
|
|||||||
// Spacer()
|
// Spacer()
|
||||||
VStack {
|
VStack {
|
||||||
// Spacer()
|
// Spacer()
|
||||||
ButtonView(button: .back) // Adding the + button
|
ButtonView(button: .back) // Adding the - button
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
VStack {
|
VStack {
|
||||||
// Spacer()
|
// Spacer()
|
||||||
ButtonView(button: .start) // Adding the - button
|
ButtonView(button: .start) // Adding the + button
|
||||||
}
|
}
|
||||||
// Spacer()
|
// Spacer()
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ struct EmulationView: View {
|
|||||||
if isAirplaying {
|
if isAirplaying {
|
||||||
Text("")
|
Text("")
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
Air.play(AnyView(MetalView(airplay: true).ignoresSafeArea()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MetalView() // The Emulation View
|
MetalView(airplay: false) // The Emulation View
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,22 @@ import MetalKit
|
|||||||
|
|
||||||
struct MetalView: UIViewRepresentable {
|
struct MetalView: UIViewRepresentable {
|
||||||
|
|
||||||
|
var airplay: Bool // just in case :3
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
|
||||||
let metalLayer = Ryujinx.shared.metalLayer!
|
let metalLayer = Ryujinx.shared.metalLayer!
|
||||||
metalLayer.frame = Ryujinx.shared.emulationUIView.bounds
|
|
||||||
Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3
|
var view = UIView()
|
||||||
|
|
||||||
|
metalLayer.frame = view.bounds
|
||||||
|
if airplay {
|
||||||
|
metalLayer.contentsScale = view.contentScaleFactor
|
||||||
|
} else {
|
||||||
|
Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3
|
||||||
|
}
|
||||||
|
|
||||||
|
Ryujinx.shared.emulationUIView = view
|
||||||
|
|
||||||
if !Ryujinx.shared.emulationUIView.subviews.contains(where: { $0 == metalLayer }) {
|
if !Ryujinx.shared.emulationUIView.subviews.contains(where: { $0 == metalLayer }) {
|
||||||
Ryujinx.shared.emulationUIView.layer.addSublayer(metalLayer)
|
Ryujinx.shared.emulationUIView.layer.addSublayer(metalLayer)
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,14 @@ struct GameInfoSheet: View {
|
|||||||
.bold()
|
.bold()
|
||||||
|
|
||||||
Text("**Version:** \(game.version)")
|
Text("**Version:** \(game.version)")
|
||||||
|
Text("**Title ID:** \(game.titleId)")
|
||||||
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
UIPasteboard.general.string = game.titleId
|
||||||
|
} label: {
|
||||||
|
Text("Copy Title ID")
|
||||||
|
}
|
||||||
|
}
|
||||||
Text("**Game Size:** \(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
Text("**Game Size:** \(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
||||||
Text("**File Type:** .\(getFileType(game.fileURL))")
|
Text("**File Type:** .\(getFileType(game.fileURL))")
|
||||||
Text("**Game URL:** \(trimGameURL(game.fileURL))")
|
Text("**Game URL:** \(trimGameURL(game.fileURL))")
|
||||||
|
@ -16,7 +16,6 @@ extension UTType {
|
|||||||
struct GameLibraryView: View {
|
struct GameLibraryView: View {
|
||||||
@Binding var startemu: Game?
|
@Binding var startemu: Game?
|
||||||
// @State var importDLCs = false
|
// @State var importDLCs = false
|
||||||
@State private var games: [Game] = []
|
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@State private var isSearching = false
|
@State private var isSearching = false
|
||||||
@AppStorage("recentGames") private var recentGamesData: Data = Data()
|
@AppStorage("recentGames") private var recentGamesData: Data = Data()
|
||||||
@ -29,13 +28,18 @@ struct GameLibraryView: View {
|
|||||||
@State var isSelectingGameFile = false
|
@State var isSelectingGameFile = false
|
||||||
@State var isViewingGameInfo: Bool = false
|
@State var isViewingGameInfo: Bool = false
|
||||||
@State var gameInfo: Game?
|
@State var gameInfo: Game?
|
||||||
|
var games: Binding<[Game]> {
|
||||||
|
Binding(
|
||||||
|
get: { Ryujinx.shared.games },
|
||||||
|
set: { Ryujinx.shared.games = $0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var filteredGames: [Game] {
|
var filteredGames: [Game] {
|
||||||
if searchText.isEmpty {
|
if searchText.isEmpty {
|
||||||
return games
|
return Ryujinx.shared.games
|
||||||
}
|
}
|
||||||
return games.filter {
|
return Ryujinx.shared.games.filter {
|
||||||
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
||||||
$0.developer.localizedCaseInsensitiveContains(searchText)
|
$0.developer.localizedCaseInsensitiveContains(searchText)
|
||||||
}
|
}
|
||||||
@ -52,7 +56,7 @@ struct GameLibraryView: View {
|
|||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
if games.isEmpty {
|
if Ryujinx.shared.games.isEmpty {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Image(systemName: "gamecontroller.fill")
|
Image(systemName: "gamecontroller.fill")
|
||||||
.font(.system(size: 64))
|
.font(.system(size: 64))
|
||||||
@ -95,7 +99,7 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
LazyVStack(spacing: 2) {
|
LazyVStack(spacing: 2) {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: $games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
@ -105,7 +109,7 @@ struct GameLibraryView: View {
|
|||||||
} else {
|
} else {
|
||||||
LazyVStack(spacing: 2) {
|
LazyVStack(spacing: 2) {
|
||||||
ForEach(filteredGames) { game in
|
ForEach(filteredGames) { game in
|
||||||
GameListRow(game: game, startemu: $startemu, games: $games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
addToRecentGames(game)
|
addToRecentGames(game)
|
||||||
}
|
}
|
||||||
@ -115,7 +119,6 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
loadGames()
|
|
||||||
loadRecentGames()
|
loadRecentGames()
|
||||||
|
|
||||||
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
||||||
@ -192,7 +195,11 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
var sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
||||||
|
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
|
sharedurl = documentsUrl.absoluteString
|
||||||
|
}
|
||||||
|
print(sharedurl)
|
||||||
let furl = URL(string: sharedurl)!
|
let furl = URL(string: sharedurl)!
|
||||||
if UIApplication.shared.canOpenURL(furl) {
|
if UIApplication.shared.canOpenURL(furl) {
|
||||||
UIApplication.shared.open(furl, options: [:])
|
UIApplication.shared.open(furl, options: [:])
|
||||||
@ -262,7 +269,7 @@ struct GameLibraryView: View {
|
|||||||
let destinationURL = romsDirectory.appendingPathComponent(url.lastPathComponent)
|
let destinationURL = romsDirectory.appendingPathComponent(url.lastPathComponent)
|
||||||
try fileManager.copyItem(at: url, to: destinationURL)
|
try fileManager.copyItem(at: url, to: destinationURL)
|
||||||
|
|
||||||
loadGames()
|
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||||
} catch {
|
} catch {
|
||||||
print("Error copying game file: \(error)")
|
print("Error copying game file: \(error)")
|
||||||
}
|
}
|
||||||
@ -317,56 +324,15 @@ struct GameLibraryView: View {
|
|||||||
recentGames = []
|
recentGames = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - loads games from roms
|
|
||||||
func loadGames() {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
|
||||||
|
|
||||||
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
|
||||||
|
|
||||||
// Check if "roms" folder exists; if not, create it
|
|
||||||
if (!fileManager.fileExists(atPath: romsDirectory.path)) {
|
|
||||||
do {
|
|
||||||
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
} catch {
|
|
||||||
print("Failed to create roms directory: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
games = []
|
|
||||||
// Load games only from "roms" folder
|
|
||||||
do {
|
|
||||||
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
|
|
||||||
|
|
||||||
files.forEach { fileURLCandidate in
|
|
||||||
do {
|
|
||||||
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
|
|
||||||
let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
|
|
||||||
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
|
|
||||||
|
|
||||||
|
|
||||||
let gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
|
|
||||||
|
|
||||||
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: fileURLCandidate)
|
|
||||||
|
|
||||||
games.append(game)
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
print("Error loading games from roms folder: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Delete Game Function
|
// MARK: - Delete Game Function
|
||||||
func deleteGame(game: Game) {
|
func deleteGame(game: Game) {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
do {
|
do {
|
||||||
try fileManager.removeItem(at: game.fileURL)
|
try fileManager.removeItem(at: game.fileURL)
|
||||||
games.removeAll { $0.id == game.id }
|
Ryujinx.shared.games.removeAll { $0.id == game.id }
|
||||||
loadGames()
|
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||||
} catch {
|
} catch {
|
||||||
print("Error deleting game: \(error)")
|
print("Error deleting game: \(error)")
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ struct SettingsView: View {
|
|||||||
@Binding var onscreencontroller: Controller
|
@Binding var onscreencontroller: Controller
|
||||||
@AppStorage("useTrollStore") var useTrollStore: Bool = false
|
@AppStorage("useTrollStore") var useTrollStore: Bool = false
|
||||||
|
|
||||||
|
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
|
||||||
|
|
||||||
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
|
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
|
||||||
|
|
||||||
var memoryManagerModes = [
|
var memoryManagerModes = [
|
||||||
@ -32,9 +34,13 @@ struct SettingsView: View {
|
|||||||
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
||||||
|
|
||||||
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = false
|
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = false
|
||||||
|
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
|
||||||
|
|
||||||
@AppStorage("performacehud") var performacehud: Bool = false
|
@AppStorage("performacehud") var performacehud: Bool = false
|
||||||
|
|
||||||
|
@AppStorage("oldWindowCode") var windowCode: Bool = false
|
||||||
|
|
||||||
|
|
||||||
@State private var showResolutionInfo = false
|
@State private var showResolutionInfo = false
|
||||||
@State private var showAnisotropicInfo = false
|
@State private var showAnisotropicInfo = false
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@ -66,6 +72,12 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disablevsync) {
|
||||||
|
labelWithIcon("Disable VSync", iconName: "arrow.triangle.2.circlepath")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
|
||||||
Toggle(isOn: $config.enableTextureRecompression) {
|
Toggle(isOn: $config.enableTextureRecompression) {
|
||||||
labelWithIcon("Texture Recompression", iconName: "rectangle.compress.vertical")
|
labelWithIcon("Texture Recompression", iconName: "rectangle.compress.vertical")
|
||||||
}
|
}
|
||||||
@ -79,8 +91,7 @@ struct SettingsView: View {
|
|||||||
Toggle(isOn: $config.macroHLE) {
|
Toggle(isOn: $config.macroHLE) {
|
||||||
labelWithIcon("Macro HLE", iconName: "gearshape")
|
labelWithIcon("Macro HLE", iconName: "gearshape")
|
||||||
}.tint(.blue)
|
}.tint(.blue)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
HStack {
|
HStack {
|
||||||
@ -296,8 +307,12 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disablePTC) {
|
||||||
|
labelWithIcon("Disable PTC", iconName: "cpu")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
if let cpuInfo = getCPUInfo(), cpuInfo.hasPrefix("Apple M") {
|
if let cpuInfo = getCPUInfo(), cpuInfo.hasPrefix("Apple M") {
|
||||||
if #available (iOS 16.4, *), getEntitlementValue("com.apple.private.hypervisor") {
|
if #available (iOS 16.4, *) {
|
||||||
Toggle(isOn: .constant(false)) {
|
Toggle(isOn: .constant(false)) {
|
||||||
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
||||||
}
|
}
|
||||||
@ -306,19 +321,18 @@ struct SettingsView: View {
|
|||||||
.onAppear() {
|
.onAppear() {
|
||||||
print("CPU Info: \(cpuInfo)")
|
print("CPU Info: \(cpuInfo)")
|
||||||
}
|
}
|
||||||
} else {
|
} else if getEntitlementValue("com.apple.private.hypervisor") {
|
||||||
Toggle(isOn: $config.hypervisor) {
|
Toggle(isOn: $config.hypervisor) {
|
||||||
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
print("CPU Info: \(cpuInfo)")
|
print("CPU Info: \(cpuInfo)")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("CPU Mode")
|
Text("CPU")
|
||||||
.font(.title3.weight(.semibold))
|
.font(.title3.weight(.semibold))
|
||||||
.textCase(nil)
|
.textCase(nil)
|
||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
@ -354,21 +368,68 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
|
|
||||||
Toggle(isOn: $useTrollStore) {
|
if #available(iOS 17.0.1, *) {
|
||||||
labelWithIcon("TrollStore", iconName: "troll.svg")
|
Toggle(isOn: $jitStreamerEB) {
|
||||||
|
labelWithIcon("JitStreamer EB", iconName: "bolt.heart")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
if let mainWindow = UIApplication.shared.windows.last {
|
||||||
|
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
||||||
|
|
||||||
|
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
||||||
|
UIApplication.shared.open(URL(string: "https://jkcoxson.com/jitstreamer")!)
|
||||||
|
}
|
||||||
|
alertController.addAction(learnMoreButton)
|
||||||
|
|
||||||
|
let doneButton = UIAlertAction(title: "Done", style: .cancel, handler: nil)
|
||||||
|
alertController.addAction(doneButton)
|
||||||
|
|
||||||
|
mainWindow.rootViewController?.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("About")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toggle(isOn: $useTrollStore) {
|
||||||
|
labelWithIcon("TrollStore JIT", iconName: "troll.svg")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
|
||||||
|
|
||||||
Toggle(isOn: $config.debuglogs) {
|
Toggle(isOn: $syncqsubmits) {
|
||||||
labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble")
|
labelWithIcon("MVK: Synchronous Queue Submits", iconName: "line.diagonal")
|
||||||
|
}.tint(.blue)
|
||||||
|
.contextMenu() {
|
||||||
|
Button {
|
||||||
|
if let mainWindow = UIApplication.shared.windows.last {
|
||||||
|
let alertController = UIAlertController(title: "About MVK: Synchronous Queue Submits", message: "Enable this option if Mario Kart 8 is crashing at Grand Prix mode.", preferredStyle: .alert)
|
||||||
|
|
||||||
|
let doneButton = UIAlertAction(title: "OK", style: .cancel, handler: nil)
|
||||||
|
alertController.addAction(doneButton)
|
||||||
|
|
||||||
|
mainWindow.rootViewController?.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("About")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisclosureGroup {
|
||||||
|
Toggle(isOn: $config.debuglogs) {
|
||||||
|
labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.tracelogs) {
|
||||||
|
labelWithIcon("Trace Logs", iconName: "waveform.path")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
} label: {
|
||||||
|
Text("Logs")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
|
||||||
|
|
||||||
Toggle(isOn: $config.tracelogs) {
|
|
||||||
labelWithIcon("Trace Logs", iconName: "waveform.path")
|
|
||||||
}
|
|
||||||
.tint(.blue)
|
|
||||||
|
|
||||||
|
|
||||||
} header: {
|
} header: {
|
||||||
Text("Miscellaneous Options")
|
Text("Miscellaneous Options")
|
||||||
@ -376,17 +437,30 @@ struct SettingsView: View {
|
|||||||
.textCase(nil)
|
.textCase(nil)
|
||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Enable trace and debug logs for troubleshooting, enable Screenshotting without distractions and Enable automatic TrollStore JIT.")
|
Text("Enable trace and debug logs for advanced troubleshooting (Note: This degrades performance),\nEnable Screenshot Button for better screenshots\nand Enable TrollStore for automatic TrollStore JIT.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advanced
|
// Advanced
|
||||||
Section {
|
Section {
|
||||||
|
labelWithIcon("JIT Acquisition: \(isJITEnabled() ? "Acquired" : "Not Acquired" )", iconName: "bolt.fill")
|
||||||
|
|
||||||
|
if #unavailable(iOS 17) {
|
||||||
|
Toggle(isOn: $windowCode) {
|
||||||
|
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
}
|
||||||
|
|
||||||
DisclosureGroup {
|
DisclosureGroup {
|
||||||
|
|
||||||
Toggle(isOn: $mVKPreFillBuffer) {
|
Toggle(isOn: $mVKPreFillBuffer) {
|
||||||
labelWithIcon("MVK: Pre-Fill Metal Command Buffers", iconName: "gearshape")
|
labelWithIcon("MVK: Pre-Fill Metal Command Buffers", iconName: "gearshape")
|
||||||
}.tint(.blue)
|
}.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.dfsIntegrityChecks) {
|
||||||
|
labelWithIcon("Disable FS Integrity Checks", iconName: "checkmark.shield")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
labelWithIcon("Page Size", iconName: "textformat.size")
|
labelWithIcon("Page Size", iconName: "textformat.size")
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -427,7 +501,11 @@ struct SettingsView: View {
|
|||||||
.textCase(nil)
|
.textCase(nil)
|
||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing)")
|
if #available(iOS 17, *) {
|
||||||
|
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing).")
|
||||||
|
} else {
|
||||||
|
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). If the emulation is not showing (you may hear audio in some games), try enabling \"SDL Window\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// RyujinxKeyboard.h
|
||||||
|
// RyujinxKeyboard
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 11/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
//! Project version number for RyujinxKeyboard.
|
||||||
|
FOUNDATION_EXPORT double RyujinxKeyboardVersionNumber;
|
||||||
|
|
||||||
|
//! Project version string for RyujinxKeyboard.
|
||||||
|
FOUNDATION_EXPORT const unsigned char RyujinxKeyboardVersionString[];
|
||||||
|
|
||||||
|
// In this header, you should import all the public headers of your framework using statements like #import <RyujinxKeyboard/PublicHeader.h>
|
||||||
|
|
||||||
|
|
Binary file not shown.
@ -0,0 +1,6 @@
|
|||||||
|
framework module RyujinxKeyboard {
|
||||||
|
umbrella header "RyujinxKeyboard.h"
|
||||||
|
export *
|
||||||
|
|
||||||
|
module * { export * }
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,124 @@
|
|||||||
|
<?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>Headers/RyujinxKeyboard.h</key>
|
||||||
|
<data>
|
||||||
|
5P7GN4g050n199pV6/+SpfMBgJc=
|
||||||
|
</data>
|
||||||
|
<key>Info.plist</key>
|
||||||
|
<data>
|
||||||
|
hYdI/ktAKwjBSfaJpt6Yc8UKLCY=
|
||||||
|
</data>
|
||||||
|
<key>Modules/module.modulemap</key>
|
||||||
|
<data>
|
||||||
|
0kFAMoTn+4Q1J/dM6uMLe3EhbL0=
|
||||||
|
</data>
|
||||||
|
</dict>
|
||||||
|
<key>files2</key>
|
||||||
|
<dict>
|
||||||
|
<key>Headers/RyujinxKeyboard.h</key>
|
||||||
|
<dict>
|
||||||
|
<key>hash2</key>
|
||||||
|
<data>
|
||||||
|
/yGmHq9NdBF/ruesISIj7vml0ySgoJkrFOcrw0vaIxQ=
|
||||||
|
</data>
|
||||||
|
</dict>
|
||||||
|
<key>Modules/module.modulemap</key>
|
||||||
|
<dict>
|
||||||
|
<key>hash2</key>
|
||||||
|
<data>
|
||||||
|
K+ZyxKhTI4bMVZuHBIspvd2PFqvCOlVUFYmwF96O5NQ=
|
||||||
|
</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>
|
@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// SoftwareKeyboard.h
|
|
||||||
// SoftwareKeyboard
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 19/12/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
//! Project version number for SoftwareKeyboard.
|
|
||||||
FOUNDATION_EXPORT double SoftwareKeyboardVersionNumber;
|
|
||||||
|
|
||||||
//! Project version string for SoftwareKeyboard.
|
|
||||||
FOUNDATION_EXPORT const unsigned char SoftwareKeyboardVersionString[];
|
|
||||||
|
|
||||||
// In this header, you should import all the public headers of your framework using statements like #import <SoftwareKeyboard/PublicHeader.h>
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
|||||||
// swift-interface-format-version: 1.0
|
|
||||||
// swift-compiler-version: Apple Swift version 6.0.3 effective-5.10 (swiftlang-6.0.3.1.4 clang-1600.0.30)
|
|
||||||
// swift-module-flags: -target arm64-apple-ios14.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone -enable-experimental-feature OpaqueTypeErasure -enable-bare-slash-regex -module-name SoftwareKeyboard
|
|
||||||
@_exported import SoftwareKeyboard
|
|
||||||
import Swift
|
|
||||||
import UIKit
|
|
||||||
import _Concurrency
|
|
||||||
import _StringProcessing
|
|
||||||
import _SwiftConcurrencyShims
|
|
||||||
@objc public enum KeyboardMode : Swift.UInt32 {
|
|
||||||
case `default` = 0
|
|
||||||
case numeric = 1
|
|
||||||
case ascii = 2
|
|
||||||
case fullLatin = 3
|
|
||||||
case alphabet = 4
|
|
||||||
case simplifiedChinese = 5
|
|
||||||
case traditionalChinese = 6
|
|
||||||
case korean = 7
|
|
||||||
case languageSet2 = 8
|
|
||||||
case languageSet2Latin = 9
|
|
||||||
public init?(rawValue: Swift.UInt32)
|
|
||||||
public typealias RawValue = Swift.UInt32
|
|
||||||
public var rawValue: Swift.UInt32 {
|
|
||||||
get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public struct SoftwareKeyboardUiArgs {
|
|
||||||
public var keyboardMode: SoftwareKeyboard.KeyboardMode
|
|
||||||
public var headerText: Swift.String
|
|
||||||
public var subtitleText: Swift.String
|
|
||||||
public var submitText: Swift.String
|
|
||||||
public var stringLengthMin: Swift.Int32
|
|
||||||
public var stringLengthMax: Swift.Int32
|
|
||||||
public var initialText: Swift.String?
|
|
||||||
}
|
|
||||||
extension SoftwareKeyboard.KeyboardMode : Swift.Equatable {}
|
|
||||||
extension SoftwareKeyboard.KeyboardMode : Swift.Hashable {}
|
|
||||||
extension SoftwareKeyboard.KeyboardMode : Swift.RawRepresentable {}
|
|
Binary file not shown.
@ -1,38 +0,0 @@
|
|||||||
// swift-interface-format-version: 1.0
|
|
||||||
// swift-compiler-version: Apple Swift version 6.0.3 effective-5.10 (swiftlang-6.0.3.1.4 clang-1600.0.30)
|
|
||||||
// swift-module-flags: -target arm64-apple-ios14.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone -enable-experimental-feature OpaqueTypeErasure -enable-bare-slash-regex -module-name SoftwareKeyboard
|
|
||||||
@_exported import SoftwareKeyboard
|
|
||||||
import Swift
|
|
||||||
import UIKit
|
|
||||||
import _Concurrency
|
|
||||||
import _StringProcessing
|
|
||||||
import _SwiftConcurrencyShims
|
|
||||||
@objc public enum KeyboardMode : Swift.UInt32 {
|
|
||||||
case `default` = 0
|
|
||||||
case numeric = 1
|
|
||||||
case ascii = 2
|
|
||||||
case fullLatin = 3
|
|
||||||
case alphabet = 4
|
|
||||||
case simplifiedChinese = 5
|
|
||||||
case traditionalChinese = 6
|
|
||||||
case korean = 7
|
|
||||||
case languageSet2 = 8
|
|
||||||
case languageSet2Latin = 9
|
|
||||||
public init?(rawValue: Swift.UInt32)
|
|
||||||
public typealias RawValue = Swift.UInt32
|
|
||||||
public var rawValue: Swift.UInt32 {
|
|
||||||
get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public struct SoftwareKeyboardUiArgs {
|
|
||||||
public var keyboardMode: SoftwareKeyboard.KeyboardMode
|
|
||||||
public var headerText: Swift.String
|
|
||||||
public var subtitleText: Swift.String
|
|
||||||
public var submitText: Swift.String
|
|
||||||
public var stringLengthMin: Swift.Int32
|
|
||||||
public var stringLengthMax: Swift.Int32
|
|
||||||
public var initialText: Swift.String?
|
|
||||||
}
|
|
||||||
extension SoftwareKeyboard.KeyboardMode : Swift.Equatable {}
|
|
||||||
extension SoftwareKeyboard.KeyboardMode : Swift.Hashable {}
|
|
||||||
extension SoftwareKeyboard.KeyboardMode : Swift.RawRepresentable {}
|
|
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
framework module SoftwareKeyboard {
|
|
||||||
umbrella header "SoftwareKeyboard.h"
|
|
||||||
export *
|
|
||||||
|
|
||||||
module * { export * }
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2,8 +2,29 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.stossy11.MeloNX</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>melonx</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>melonx</string>
|
||||||
|
</array>
|
||||||
<key>MeloID</key>
|
<key>MeloID</key>
|
||||||
<string>83f67a0a96bd8628a150d7853e360db5bae64e7769524fae399c4b8e7e6aff17</string>
|
<string>83f67a0a96bd8628a150d7853e360db5bae64e7769524fae399c4b8e7e6aff17</string>
|
||||||
|
<key>NSUserActivityTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>LaunchGameIntent</string>
|
||||||
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -15,12 +15,13 @@ import CryptoKit
|
|||||||
struct MeloNXApp: App {
|
struct MeloNXApp: App {
|
||||||
|
|
||||||
@State var showed = false
|
@State var showed = false
|
||||||
|
@Environment(\.scenePhase) var scenePhase
|
||||||
|
@State var alert: UIAlertController? = nil
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ZStack {
|
ZStack {
|
||||||
if showed {
|
if showed || DRM != 1 {
|
||||||
ContentView()
|
ContentView()
|
||||||
} else {
|
} else {
|
||||||
Group {
|
Group {
|
||||||
@ -61,11 +62,18 @@ struct MeloNXApp: App {
|
|||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
InitializeRyujinx() { bool in
|
InitializeRyujinx() { bool in
|
||||||
if !bool {
|
if !bool, (scenePhase != .background || scenePhase == .inactive) {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
showed = false
|
showed = false
|
||||||
}
|
}
|
||||||
showDMCAAlert()
|
if !(alert?.isViewLoaded ?? false) {
|
||||||
|
alert = showDMCAAlert()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
alert?.dismiss(animated: true)
|
||||||
|
showed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,7 +93,7 @@ struct MeloNXApp: App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func showAlert() {
|
func showAlert() -> UIAlertController? {
|
||||||
// Create the alert controller
|
// Create the alert controller
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
if let mainWindow = UIApplication.shared.windows.last {
|
||||||
let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert)
|
let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert)
|
||||||
@ -118,23 +126,28 @@ struct MeloNXApp: App {
|
|||||||
|
|
||||||
// Present the alert
|
// Present the alert
|
||||||
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
||||||
|
|
||||||
|
return alertController
|
||||||
} else {
|
} else {
|
||||||
exit(0)
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func showDMCAAlert() {
|
func showDMCAAlert() -> UIAlertController? {
|
||||||
DispatchQueue.main.async {
|
if let mainWindow = UIApplication.shared.windows.first {
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert)
|
||||||
let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert)
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
||||||
} else {
|
|
||||||
exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return alertController
|
||||||
|
} else {
|
||||||
|
// uhoh
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,43 +98,10 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
return okPressed;
|
return okPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
|
public void DisplayInputDialog(SoftwareKeyboardUiArgs args, Action<string> onTextEntered)
|
||||||
{
|
{
|
||||||
ManualResetEvent dialogCloseEvent = new(false);
|
onTextEntered?.Invoke("MeloNX");
|
||||||
|
return;
|
||||||
bool okPressed = false;
|
|
||||||
bool error = false;
|
|
||||||
string inputText = args.InitialText ?? "";
|
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
|
|
||||||
|
|
||||||
if (response.Result == UserResult.Ok)
|
|
||||||
{
|
|
||||||
inputText = response.Input;
|
|
||||||
okPressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
error = true;
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dialogCloseEvent.Set();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogCloseEvent.WaitOne();
|
|
||||||
|
|
||||||
userText = error ? null : inputText;
|
|
||||||
|
|
||||||
return error || okPressed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
|
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
|
||||||
|
@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
class DescriptorSetManager : IDisposable
|
class DescriptorSetManager : IDisposable
|
||||||
{
|
{
|
||||||
public const uint MaxSets = 16;
|
public const uint MaxSets = 32;
|
||||||
|
|
||||||
public class DescriptorPoolHolder : IDisposable
|
public class DescriptorPoolHolder : IDisposable
|
||||||
{
|
{
|
||||||
|
@ -23,13 +23,9 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
|||||||
|
|
||||||
config.UseMetalArgumentBuffers = true;
|
config.UseMetalArgumentBuffers = true;
|
||||||
|
|
||||||
if (OperatingSystem.IsIOSVersionAtLeast(17)) {
|
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
||||||
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.MaxActiveMetalCommandBuffersPerQueue = 1024;
|
config.MaxActiveMetalCommandBuffersPerQueue = 1024;
|
||||||
|
|
||||||
config.SynchronousQueueSubmits = false;
|
|
||||||
|
|
||||||
config.ResumeLostDevice = true;
|
config.ResumeLostDevice = true;
|
||||||
|
|
||||||
|
@ -1255,7 +1255,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
int vbSize = vertexBuffer.Buffer.Size;
|
int vbSize = vertexBuffer.Buffer.Size;
|
||||||
|
|
||||||
if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
|
if ((Gd.Vendor == Vendor.Amd || !OperatingSystem.IsIOSVersionAtLeast(17)) && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
|
||||||
{
|
{
|
||||||
// AMD has a bug where if offset + stride * count is greater than
|
// AMD has a bug where if offset + stride * count is greater than
|
||||||
// the size, then the last attribute will have the wrong value.
|
// the size, then the last attribute will have the wrong value.
|
||||||
|
@ -14,6 +14,8 @@ using System.IO;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
@ -51,10 +53,10 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
|
|
||||||
private byte[] _transferMemory;
|
private byte[] _transferMemory;
|
||||||
|
|
||||||
private string _textValue = "";
|
public string _textValue = "";
|
||||||
private int _cursorBegin = 0;
|
private int _cursorBegin = 0;
|
||||||
private Encoding _encoding = Encoding.Unicode;
|
private Encoding _encoding = Encoding.Unicode;
|
||||||
private KeyboardResult _lastResult = KeyboardResult.NotSet;
|
public KeyboardResult _lastResult = KeyboardResult.NotSet;
|
||||||
|
|
||||||
private IDynamicTextInputHandler _dynamicTextInputHandler = null;
|
private IDynamicTextInputHandler _dynamicTextInputHandler = null;
|
||||||
private SoftwareKeyboardRenderer _keyboardRenderer = null;
|
private SoftwareKeyboardRenderer _keyboardRenderer = null;
|
||||||
@ -180,9 +182,6 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("SoftwareKeyboard.framework/SoftwareKeyboard", EntryPoint = "displayInputDialog", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
public static extern void DisplayInputDialog(ref SoftwareKeyboardUiArgs args, out IntPtr userInput);
|
|
||||||
|
|
||||||
|
|
||||||
private void ExecuteForegroundKeyboard()
|
private void ExecuteForegroundKeyboard()
|
||||||
{
|
{
|
||||||
@ -223,26 +222,8 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
InitialText = initialText,
|
InitialText = initialText,
|
||||||
};
|
};
|
||||||
|
|
||||||
IntPtr userInputPtr;
|
_textValue = DefaultInputText;
|
||||||
|
_lastResult = KeyboardResult.Cancel;
|
||||||
DisplayInputDialog(ref args, out userInputPtr);
|
|
||||||
if (userInputPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
// Convert the IntPtr to a string
|
|
||||||
string userInput = Marshal.PtrToStringAnsi(userInputPtr);
|
|
||||||
|
|
||||||
_textValue = userInput ?? DefaultInputText;
|
|
||||||
_lastResult = KeyboardResult.Accept;
|
|
||||||
|
|
||||||
Console.WriteLine($"User input: {userInput}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("No input was received or input was canceled.");
|
|
||||||
|
|
||||||
_textValue = DefaultInputText;
|
|
||||||
_lastResult = KeyboardResult.Cancel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -259,37 +240,40 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
StringLengthMax = _keyboardForegroundConfig.StringLengthMax,
|
StringLengthMax = _keyboardForegroundConfig.StringLengthMax,
|
||||||
InitialText = initialText,
|
InitialText = initialText,
|
||||||
};
|
};
|
||||||
|
_device.UiHandler.DisplayInputDialog(args, inputText =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"User entered: {inputText}");
|
||||||
|
|
||||||
|
_textValue = inputText ?? initialText ?? DefaultInputText;
|
||||||
|
_lastResult = !string.IsNullOrEmpty(inputText) ? KeyboardResult.Accept : KeyboardResult.Cancel;
|
||||||
|
|
||||||
_lastResult = _device.UiHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel;
|
while (_textValue.Length < _keyboardForegroundConfig.StringLengthMin)
|
||||||
_textValue ??= initialText ?? DefaultInputText;
|
{
|
||||||
}
|
_textValue = string.Join(" ", _textValue, _textValue);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the text meets the minimum length requirement
|
// Truncate the text if it exceeds the maximum length
|
||||||
while (_textValue.Length < _keyboardForegroundConfig.StringLengthMin)
|
if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax)
|
||||||
{
|
{
|
||||||
_textValue = string.Join(" ", _textValue, _textValue);
|
_textValue = _textValue[.._keyboardForegroundConfig.StringLengthMax];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate the text if it exceeds the maximum length
|
// Handle text validation if required
|
||||||
if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax)
|
if (_keyboardForegroundConfig.CheckText)
|
||||||
{
|
{
|
||||||
_textValue = _textValue[.._keyboardForegroundConfig.StringLengthMax];
|
// Submit text for validation
|
||||||
}
|
_foregroundState = SoftwareKeyboardState.ValidationPending;
|
||||||
|
PushForegroundResponse(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Submit text as complete
|
||||||
|
_foregroundState = SoftwareKeyboardState.Complete;
|
||||||
|
PushForegroundResponse(false);
|
||||||
|
|
||||||
// Handle text validation if required
|
AppletStateChanged?.Invoke(this, null);
|
||||||
if (_keyboardForegroundConfig.CheckText)
|
}
|
||||||
{
|
});
|
||||||
// Submit text for validation
|
|
||||||
_foregroundState = SoftwareKeyboardState.ValidationPending;
|
|
||||||
PushForegroundResponse(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Submit text as complete
|
|
||||||
_foregroundState = SoftwareKeyboardState.Complete;
|
|
||||||
PushForegroundResponse(false);
|
|
||||||
|
|
||||||
AppletStateChanged?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.HLE.HOS.Applets;
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Ui
|
namespace Ryujinx.HLE.Ui
|
||||||
{
|
{
|
||||||
@ -10,7 +11,7 @@ namespace Ryujinx.HLE.Ui
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
|
/// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
|
||||||
/// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
|
/// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
|
||||||
bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText);
|
public void DisplayInputDialog(SoftwareKeyboardUiArgs args, Action<string> onTextEntered);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Displays a Message Dialog box to the user and blocks until it is closed.
|
/// Displays a Message Dialog box to the user and blocks until it is closed.
|
||||||
|
42
src/Ryujinx.Headless.SDL2/Keyboard-iOS.cs
Normal file
42
src/Ryujinx.Headless.SDL2/Keyboard-iOS.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Headless.SDL2
|
||||||
|
{
|
||||||
|
public static class AlertHelper
|
||||||
|
{
|
||||||
|
[DllImport("RyujinxKeyboard.framework/RyujinxKeyboard", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void showKeyboardAlert(string title, string message, string placeholder);
|
||||||
|
|
||||||
|
[DllImport("RyujinxKeyboard.framework/RyujinxKeyboard", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern IntPtr getKeyboardInput();
|
||||||
|
|
||||||
|
[DllImport("RyujinxKeyboard.framework/RyujinxKeyboard", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
private static extern void clearKeyboardInput();
|
||||||
|
|
||||||
|
public static void ShowAlertWithTextInput(string title, string message, string placeholder, Action<string> onTextEntered)
|
||||||
|
{
|
||||||
|
showKeyboardAlert(title, message, placeholder);
|
||||||
|
|
||||||
|
ThreadPool.QueueUserWorkItem(_ =>
|
||||||
|
{
|
||||||
|
string result = null;
|
||||||
|
while (result == null)
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
|
||||||
|
IntPtr inputPtr = getKeyboardInput();
|
||||||
|
if (inputPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
result = Marshal.PtrToStringAnsi(inputPtr);
|
||||||
|
clearKeyboardInput();
|
||||||
|
|
||||||
|
onTextEntered?.Invoke(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -460,12 +460,19 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
Exit();
|
Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
|
public void DisplayInputDialog(SoftwareKeyboardUiArgs args, Action<string> onTextEntered)
|
||||||
{
|
{
|
||||||
// SDL2 doesn't support input dialogs
|
// SDL2 doesn't support input dialogs
|
||||||
userText = "Ryujinx";
|
// Trying to use Objective-C on iDevices
|
||||||
|
if (OperatingSystem.IsIOS())
|
||||||
return true;
|
{
|
||||||
|
AlertHelper.ShowAlertWithTextInput(args.HeaderText, args.SubtitleText, args.GuideText, (inputText) =>
|
||||||
|
{
|
||||||
|
onTextEntered?.Invoke(inputText);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onTextEntered?.Invoke("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayMessageDialog(string title, string message)
|
public bool DisplayMessageDialog(string title, string message)
|
||||||
|
@ -81,57 +81,10 @@ namespace Ryujinx.Ui.Applet
|
|||||||
return okPressed;
|
return okPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
|
public void DisplayInputDialog(SoftwareKeyboardUiArgs args, Action<string> onTextEntered)
|
||||||
{
|
{
|
||||||
ManualResetEvent dialogCloseEvent = new(false);
|
onTextEntered?.Invoke("MeloNX");
|
||||||
|
return;
|
||||||
bool okPressed = false;
|
|
||||||
bool error = false;
|
|
||||||
string inputText = args.InitialText ?? "";
|
|
||||||
|
|
||||||
Application.Invoke(delegate
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var swkbdDialog = new SwkbdAppletDialog(_parent)
|
|
||||||
{
|
|
||||||
Title = "Software Keyboard",
|
|
||||||
Text = args.HeaderText,
|
|
||||||
SecondaryText = args.SubtitleText,
|
|
||||||
};
|
|
||||||
|
|
||||||
swkbdDialog.InputEntry.Text = inputText;
|
|
||||||
swkbdDialog.InputEntry.PlaceholderText = args.GuideText;
|
|
||||||
swkbdDialog.OkButton.Label = args.SubmitText;
|
|
||||||
|
|
||||||
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
|
||||||
swkbdDialog.SetInputValidation(args.KeyboardMode);
|
|
||||||
|
|
||||||
if (swkbdDialog.Run() == (int)ResponseType.Ok)
|
|
||||||
{
|
|
||||||
inputText = swkbdDialog.InputEntry.Text;
|
|
||||||
okPressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
swkbdDialog.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
error = true;
|
|
||||||
|
|
||||||
GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dialogCloseEvent.Set();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogCloseEvent.WaitOne();
|
|
||||||
|
|
||||||
userText = error ? null : inputText;
|
|
||||||
|
|
||||||
return error || okPressed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value)
|
public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user