forked from MeloNX/MeloNX
Compare commits
129 Commits
greem-melo
...
XC-ios-ht
Author | SHA1 | Date | |
---|---|---|---|
4ffb0ff617 | |||
a358dcdfc4 | |||
08ee9b18ea | |||
aadc258187 | |||
1c75d22190 | |||
57c297369a | |||
56544db198 | |||
6ec2ad2841 | |||
9d6c7d9900 | |||
9ddc6a969c | |||
|
1b69c0bdc6 | ||
|
18d98755f6 | ||
|
c6de4abce3 | ||
|
e5c5e8572e | ||
c0e8570293 | |||
c8a3124cca | |||
2c389c899a | |||
11571aca6e | |||
|
e04e689bc4 | ||
5c903626cc | |||
9ca187a8c4 | |||
cac3853d96 | |||
fff70a2dba | |||
4da30e332c | |||
|
114ba3eb57 | ||
|
839ddab589 | ||
|
00a06c4dc8 | ||
efbeebafcb | |||
|
b85758ba88 | ||
|
46196daf39 | ||
|
eb4a4593ea | ||
|
c3ade6f5cd | ||
|
007cb026a4 | ||
|
4f3e49a90c | ||
|
2d5f1d8015 | ||
|
f57d24706b | ||
|
a2c3f6d624 | ||
|
9c014e6f87 | ||
|
c8db129402 | ||
|
0b6518d7e3 | ||
|
cb114fbb68 | ||
|
f2ea6448dc | ||
|
9fa29efaf4 | ||
|
a166494e33 | ||
1d16bf0c94 | |||
|
7008ce4f23 | ||
|
52fd0bf79b | ||
|
0cc5476d87 | ||
7dde0d254a | |||
|
11305c2aac | ||
|
9ce29d6ad1 | ||
|
5ee90c81e9 | ||
|
26fe33703d | ||
|
57d0b27586 | ||
|
b7f6094b60 | ||
|
ec29829296 | ||
|
1c6c083163 | ||
|
27aaea0d68 | ||
|
994f6c0732 | ||
|
c5131d9850 | ||
|
09a757c445 | ||
|
71551adf2d | ||
|
d13dc50a10 | ||
|
2901f462aa | ||
|
160a58e127 | ||
|
9eae1ab594 | ||
|
d2e406fa56 | ||
|
054cb50b22 | ||
|
05b131b33f | ||
|
ccf89aa324 | ||
|
ace6616067 | ||
|
0968360e08 | ||
|
81941f9e9f | ||
|
6e7e5dbfca | ||
|
e76e927b28 | ||
|
b6bad055a8 | ||
|
2fbe6eb9da | ||
|
86c93fe163 | ||
|
2e6e4eb2a0 | ||
|
438c1a896f | ||
|
c5736f9b15 | ||
|
1662bcbc96 | ||
|
63427eb744 | ||
|
06f3c6d20e | ||
|
3e657d7229 | ||
|
ec16e150f6 | ||
|
aca5ee8305 | ||
|
a61e2a3992 | ||
1735216de6 | |||
20547bc412 | |||
|
ed027f1649 | ||
|
e02037d9c3 | ||
|
e74ab3a602 | ||
|
7025c32c4a | ||
|
de19cc29d8 | ||
|
f55d596688 | ||
|
209d0f1a15 | ||
|
db86daef39 | ||
|
9e09cb5767 | ||
b089fda22d | |||
|
94dc643f26 | ||
|
e81ee8f8bf | ||
|
fdbcc483b3 | ||
|
5163737886 | ||
|
6a45d469db | ||
|
658bdd7bec | ||
|
d64ef5eed9 | ||
|
11c3d31764 | ||
|
61344e731e | ||
|
ddcb7a8f77 | ||
|
531446a6ce | ||
|
249e7104f6 | ||
|
3e0c86b047 | ||
|
51a2dfd27d | ||
|
31b10799a3 | ||
|
11ec203e9f | ||
|
de6c0a43b0 | ||
|
663ec73028 | ||
|
aed7a06f0d | ||
|
e0785922d5 | ||
|
abcad02f3e | ||
a5a543f06c | |||
|
c000541be1 | ||
|
4149c329ea | ||
|
300efe5f55 | ||
|
bb4e7314a5 | ||
|
73f14cf59c | ||
|
464f14f143 | ||
|
860d4d363d |
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
|
||||||
|
}'
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
|
|
||||||
|
dotnet.xcconfig
|
||||||
|
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
[Rr]elease/
|
[Rr]elease/
|
||||||
x64/
|
x64/
|
||||||
|
80
Compile.md
Normal file
80
Compile.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Compiling MeloNX on macOS
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you begin, ensure you have the following installed:
|
||||||
|
|
||||||
|
- [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||||
|
- A Mac running **macOS**
|
||||||
|
|
||||||
|
## Compilation Steps
|
||||||
|
|
||||||
|
### 1. Clone the Repository and Build Ryujinx
|
||||||
|
|
||||||
|
Open a terminal and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://git.743378673.xyz/MeloNX/MeloNX.git
|
||||||
|
cd MeloNX
|
||||||
|
./compile.sh
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
However, if you only need to update MeloNX, make sure you have cd into the directory then run this then skip to step 5
|
||||||
|
```
|
||||||
|
git pull
|
||||||
|
./compile.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 **selected** (Next to MeloNX with the arrow) in Xcode.
|
||||||
|
- You may need to install the iOS SDK. it will say next to MeloNX with the arrow saying "iOS XX Not Installed (GET)"
|
||||||
|
- You will be need to press GET and wait for it to finish downloading and installing
|
||||||
|
- Then you will be able to select your device and Build and Run.
|
||||||
|
|
||||||
|
Make Sure you do **NOT** select the Simulator. (Which is the Generic names and the ones with the non-coloured icons, e.g. "iPhone 16 Pro")
|
||||||
|
|
||||||
|
### 6. Build and Run
|
||||||
|
|
||||||
|
Click the **Run (▶️) button** in Xcode to compile and launch MeloNX.
|
||||||
|
- When running on your device, Click the **Spray Can Button** below the Run button
|
||||||
|
- Right Click where it says "> MeloNX PID XXXX"
|
||||||
|
- Press Detach in the Context Menu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Now you're all set! 🚀 If you encounter issues, please join the discord at https://melonx.org
|
||||||
|
```
|
14
LICENSE.txt
14
LICENSE.txt
@ -1,9 +1,15 @@
|
|||||||
MIT License
|
MeloNX License
|
||||||
|
|
||||||
Copyright (c) Ryujinx Team and Contributors
|
Copyright (c) MeloNX Team and Contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person (except anyone who has previously attempted or is currently attempting to merge MeloNX with Pomelo) obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Every file is under this license, and all copies must be redistributed under the same license.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Anyone who attempts or has attempted to merge MeloNX with Pomelo, or otherwise use this source code in conjunction with Pomelo, is prohibited from using, copying, modifying, or distributing the source code without first obtaining explicit, written permission from Stossy11.
|
||||||
|
|
||||||
|
Additionally, the names of the developers or contributors to this project may not be used to endorse or promote products derived from this software without specific, prior written permission from the respective developer(s).
|
||||||
|
|
||||||
|
Ryujinx is licensed under the MIT License. Copyright (c) Ryujinx contributors. All rights to Ryujinx are held by its respective copyright holders, and its use is subject to the terms of the MIT License.
|
46
MeloNX-hv.entitlements
Normal file
46
MeloNX-hv.entitlements
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.private.iokit.IOServiceSetAuthorizationID</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.exception.iokit-user-client-class</key>
|
||||||
|
<array>
|
||||||
|
<string>AGXCommandQueue</string>
|
||||||
|
<string>AGXDevice</string>
|
||||||
|
<string>AGXDeviceUserClient</string>
|
||||||
|
<string>AGXSharedUserClient</string>
|
||||||
|
<string>AppleUSBHostDeviceUserClient</string>
|
||||||
|
<string>AppleUSBHostInterfaceUserClient</string>
|
||||||
|
<string>IOSurfaceRootUserClient</string>
|
||||||
|
<string>IOAccelContext</string>
|
||||||
|
<string>IOAccelContext2</string>
|
||||||
|
<string>IOAccelDevice</string>
|
||||||
|
<string>IOAccelDevice2</string>
|
||||||
|
<string>IOAccelSharedUserClient</string>
|
||||||
|
<string>IOAccelSharedUserClient2</string>
|
||||||
|
<string>IOAccelSubmitter2</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.system.diagnostics.iokit-properties</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.vm.device-access</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.private.hypervisor</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.private.memorystatus</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.private.security.no-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.private.security.storage.AppDataContainers</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.private.security.storage.MobileDocuments</key>
|
||||||
|
<true/>
|
||||||
|
<key>platform-application</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
113
README.md
113
README.md
@ -1,22 +1,121 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices.
|
MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices.
|
||||||
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">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">MeloNX license (Based on MIT)</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/" 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 [MeloNX license (Based on 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.
|
||||||
|
@ -24,13 +24,13 @@ namespace ARMeilleure.Native
|
|||||||
public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
|
public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
|
||||||
// When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
|
// When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
|
||||||
|
|
||||||
//pthread_jit_write_protect_np(0);
|
// pthread_jit_write_protect_np(0);
|
||||||
|
|
||||||
var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
|
var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
|
||||||
var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
|
var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
|
||||||
srcSpan.CopyTo(dstSpan);
|
srcSpan.CopyTo(dstSpan);
|
||||||
|
|
||||||
//pthread_jit_write_protect_np(1);
|
// pthread_jit_write_protect_np(1);
|
||||||
|
|
||||||
// Ensure that the instruction cache for this range is invalidated.
|
// Ensure that the instruction cache for this range is invalidated.
|
||||||
sys_icache_invalidate(dst, (IntPtr)n);
|
sys_icache_invalidate(dst, (IntPtr)n);
|
||||||
|
@ -15,11 +15,11 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
static partial class JitCache
|
static partial class JitCache
|
||||||
{
|
{
|
||||||
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
||||||
private static readonly int _pageMask = _pageSize - 1;
|
private static readonly int _pageMask = _pageSize - 4;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 2047 * 1024 * 1024;
|
private const int CacheSize = 1024 * 1024 * 1024;
|
||||||
private const int CacheSizeIOS = 512 * 1024 * 1024;
|
private const int CacheSizeIOS = 128 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
private static ReservedRegion _jitRegion;
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
@ -3,12 +3,30 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 73;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXAggregateTarget section */
|
||||||
|
BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */ = {
|
||||||
|
isa = PBXAggregateTarget;
|
||||||
|
buildConfigurationList = BD43C6222D1B248D003BBC42 /* Build configuration list for PBXAggregateTarget "com.Stossy11.MeloNX.RyujinxAg" */;
|
||||||
|
buildPhases = (
|
||||||
|
BD43C62A2D1B252F003BBC42 /* ShellScript */,
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = com.Stossy11.MeloNX.RyujinxAg;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = com.Stossy11.MeloNX.RyujinxAg;
|
||||||
|
};
|
||||||
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||||
4E80AA212CD705DD00029585 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E80AA202CD705DD00029585 /* SDL */; };
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
||||||
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -26,9 +44,26 @@
|
|||||||
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
||||||
remoteInfo = MeloNX;
|
remoteInfo = MeloNX;
|
||||||
};
|
};
|
||||||
|
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = BD43C61D2D1B23AB003BBC42;
|
||||||
|
remoteInfo = Ryujinx;
|
||||||
|
};
|
||||||
/* 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;
|
||||||
@ -42,14 +77,17 @@
|
|||||||
/* 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; };
|
||||||
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
||||||
|
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = dotnet.xcconfig.example; sourceTree = "<group>"; };
|
||||||
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */ = {
|
CA8F9C2D2D3F5A3A00D7E586 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
Info.plist,
|
Info.plist,
|
||||||
@ -59,33 +97,83 @@
|
|||||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||||
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = {
|
CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
|
||||||
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
||||||
attributesByRelativePath = {
|
attributesByRelativePath = {
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
|
"Dependencies/Dynamic Libraries/Hypervisor.framework" = (
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (CodeSignOnCopy, );
|
CodeSignOnCopy,
|
||||||
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (CodeSignOnCopy, );
|
RemoveHeadersOnCopy,
|
||||||
"Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, );
|
);
|
||||||
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
|
||||||
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
CodeSignOnCopy,
|
||||||
Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
);
|
||||||
Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework" = (
|
||||||
Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
CodeSignOnCopy,
|
||||||
Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
RemoveHeadersOnCopy,
|
||||||
Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
);
|
||||||
Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (
|
||||||
Dependencies/XCFrameworks/libteakra.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
CodeSignOnCopy,
|
||||||
|
);
|
||||||
|
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
);
|
||||||
|
"Dependencies/Dynamic Libraries/libavutil.dylib" = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/MoltenVK.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/SDL2.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libSPIRV.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavcodec.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavfilter.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavformat.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavutil.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libswresample.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libswscale.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libteakra.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
|
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
|
"Dependencies/Dynamic Libraries/Hypervisor.framework",
|
||||||
"Dependencies/Dynamic Libraries/libavcodec.dylib",
|
"Dependencies/Dynamic Libraries/libavcodec.dylib",
|
||||||
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
||||||
|
"Dependencies/Dynamic Libraries/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,
|
||||||
Dependencies/XCFrameworks/libavutil.xcframework,
|
Dependencies/XCFrameworks/libavutil.xcframework,
|
||||||
|
Dependencies/XCFrameworks/libSPIRV.xcframework,
|
||||||
Dependencies/XCFrameworks/libswresample.xcframework,
|
Dependencies/XCFrameworks/libswresample.xcframework,
|
||||||
Dependencies/XCFrameworks/libswscale.xcframework,
|
Dependencies/XCFrameworks/libswscale.xcframework,
|
||||||
Dependencies/XCFrameworks/libteakra.xcframework,
|
Dependencies/XCFrameworks/libteakra.xcframework,
|
||||||
@ -96,25 +184,9 @@
|
|||||||
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */ = {
|
4E80A98F2CD6F54500029585 /* MeloNX */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CA8F9C2D2D3F5A3A00D7E586 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MeloNX; sourceTree = "<group>"; };
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXTests; sourceTree = "<group>"; };
|
||||||
exceptions = (
|
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXUITests; sourceTree = "<group>"; };
|
||||||
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
|
|
||||||
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */,
|
|
||||||
);
|
|
||||||
path = MeloNX;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = MeloNXTests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = MeloNXUITests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -122,8 +194,9 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */,
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
||||||
4E80AA212CD705DD00029585 /* SDL in Frameworks */,
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
||||||
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -147,6 +220,8 @@
|
|||||||
4E80A9842CD6F54500029585 = {
|
4E80A9842CD6F54500029585 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */,
|
||||||
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
||||||
4E80A9AA2CD6F54700029585 /* MeloNXUITests */,
|
4E80A9AA2CD6F54700029585 /* MeloNXUITests */,
|
||||||
@ -168,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;
|
||||||
@ -175,6 +251,25 @@
|
|||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXLegacyTarget section */
|
||||||
|
BD43C61D2D1B23AB003BBC42 /* Ryujinx */ = {
|
||||||
|
isa = PBXLegacyTarget;
|
||||||
|
buildArgumentsString = "publish -c Release -r ios-arm64 -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true";
|
||||||
|
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
|
||||||
|
buildPhases = (
|
||||||
|
);
|
||||||
|
buildToolPath = "$(DOTNET_PATH)";
|
||||||
|
buildWorkingDirectory = "$(SRCROOT)/../..";
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Ryujinx;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
passBuildSettingsInEnvironment = 0;
|
||||||
|
productName = Ryujinx;
|
||||||
|
};
|
||||||
|
/* End PBXLegacyTarget section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
4E80A98C2CD6F54500029585 /* MeloNX */ = {
|
4E80A98C2CD6F54500029585 /* MeloNX */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
@ -184,6 +279,7 @@
|
|||||||
4E80A98A2CD6F54500029585 /* Frameworks */,
|
4E80A98A2CD6F54500029585 /* Frameworks */,
|
||||||
4E80A98B2CD6F54500029585 /* Resources */,
|
4E80A98B2CD6F54500029585 /* Resources */,
|
||||||
4E80AA092CD6FAA800029585 /* Embed Libraries */,
|
4E80AA092CD6FAA800029585 /* Embed Libraries */,
|
||||||
|
4E50F49E2D5CC28B0080F1D1 /* Embed Watch Content */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -194,7 +290,8 @@
|
|||||||
);
|
);
|
||||||
name = MeloNX;
|
name = MeloNX;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
4E80AA202CD705DD00029585 /* SDL */,
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */,
|
||||||
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
||||||
);
|
);
|
||||||
productName = MeloNX;
|
productName = MeloNX;
|
||||||
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
||||||
@ -253,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 = {
|
||||||
@ -267,6 +364,12 @@
|
|||||||
CreatedOnToolsVersion = 16.1;
|
CreatedOnToolsVersion = 16.1;
|
||||||
TestTargetID = 4E80A98C2CD6F54500029585;
|
TestTargetID = 4E80A98C2CD6F54500029585;
|
||||||
};
|
};
|
||||||
|
BD43C61D2D1B23AB003BBC42 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
|
BD43C6212D1B248D003BBC42 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 4E80A9882CD6F54500029585 /* Build configuration list for PBXProject "MeloNX" */;
|
buildConfigurationList = 4E80A9882CD6F54500029585 /* Build configuration list for PBXProject "MeloNX" */;
|
||||||
@ -279,9 +382,10 @@
|
|||||||
mainGroup = 4E80A9842CD6F54500029585;
|
mainGroup = 4E80A9842CD6F54500029585;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */,
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */,
|
||||||
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 56;
|
||||||
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@ -289,6 +393,8 @@
|
|||||||
4E80A98C2CD6F54500029585 /* MeloNX */,
|
4E80A98C2CD6F54500029585 /* MeloNX */,
|
||||||
4E80A99C2CD6F54700029585 /* MeloNXTests */,
|
4E80A99C2CD6F54700029585 /* MeloNXTests */,
|
||||||
4E80A9A62CD6F54700029585 /* MeloNXUITests */,
|
4E80A9A62CD6F54700029585 /* MeloNXUITests */,
|
||||||
|
BD43C61D2D1B23AB003BBC42 /* Ryujinx */,
|
||||||
|
BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -317,6 +423,28 @@
|
|||||||
};
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
BD43C62A2D1B252F003BBC42 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"$(SRCROOT)/../../src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib",
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(SRCROOT)/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
4E80A9892CD6F54500029585 /* Sources */ = {
|
4E80A9892CD6F54500029585 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
@ -352,6 +480,11 @@
|
|||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
|
||||||
|
targetProxy = BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
@ -360,6 +493,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
@ -390,17 +524,20 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
EAGER_LINKING = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_FAST_MATH = YES;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
);
|
);
|
||||||
|
GCC_UNROLL_LOOPS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
@ -408,12 +545,14 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -423,6 +562,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
@ -453,11 +593,15 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
EAGER_LINKING = YES;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_FAST_MATH = YES;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
|
GCC_UNROLL_LOOPS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
@ -465,11 +609,15 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_DISABLE_SAFETY_CHECKS = YES;
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@ -485,16 +633,44 @@
|
|||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
ENABLE_TESTABILITY = NO;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
|
||||||
|
"$(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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
);
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||||
|
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "MeloNX needs access to your Photo Library in order to save images";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -514,26 +690,6 @@
|
|||||||
"$(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",
|
|
||||||
"$(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",
|
|
||||||
"$(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",
|
||||||
@ -555,11 +711,11 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
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;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@ -576,17 +732,44 @@
|
|||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 3;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
|
||||||
|
"$(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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
);
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||||
|
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "MeloNX needs access to your Photo Library in order to save images";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -606,26 +789,6 @@
|
|||||||
"$(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",
|
|
||||||
"$(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",
|
|
||||||
"$(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",
|
||||||
@ -647,11 +810,11 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
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;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@ -729,6 +892,52 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
BD43C61F2D1B23AB003BBC42 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEBUGGING_SYMBOLS = YES;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
OTHER_CFLAGS = "";
|
||||||
|
OTHER_LDFLAGS = "";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
BD43C6202D1B23AB003BBC42 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
OTHER_CFLAGS = "";
|
||||||
|
OTHER_LDFLAGS = "";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
BD43C6232D1B248D003BBC42 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
BD43C6242D1B248D003BBC42 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
@ -768,24 +977,55 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
BD43C61F2D1B23AB003BBC42 /* Debug */,
|
||||||
|
BD43C6202D1B23AB003BBC42 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
BD43C6222D1B248D003BBC42 /* Build configuration list for PBXAggregateTarget "com.Stossy11.MeloNX.RyujinxAg" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
BD43C6232D1B248D003BBC42 /* Debug */,
|
||||||
|
BD43C6242D1B248D003BBC42 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = {
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/ctreffs/SwiftSDL2";
|
repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 1.4.1;
|
minimumVersion = 1.5.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/mchoe/SwiftSVG";
|
||||||
|
requirement = {
|
||||||
|
branch = master;
|
||||||
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
4E80AA202CD705DD00029585 /* SDL */ = {
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */;
|
package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */;
|
||||||
productName = SDL;
|
productName = SwiftUIJoystick;
|
||||||
|
};
|
||||||
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
||||||
|
productName = SwiftSVG;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5",
|
"originHash" : "d611b071fbe94fdc9900a07a218340eab4ce2c3c7168bf6542f2830c0400a72b",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "swiftsdl2",
|
"identity" : "swiftsvg",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/ctreffs/SwiftSDL2",
|
"location" : "https://github.com/mchoe/SwiftSVG",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a",
|
"branch" : "master",
|
||||||
"version" : "1.4.1"
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftuijoystick",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
|
||||||
|
"version" : "1.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
Binary file not shown.
@ -0,0 +1,5 @@
|
|||||||
|
<?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">
|
||||||
|
<array/>
|
||||||
|
</plist>
|
Binary file not shown.
Binary file not shown.
BIN
src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/ls.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
BIN
src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/ls.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
<?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>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,24 @@
|
|||||||
|
<?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>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,24 @@
|
|||||||
|
<?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>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -7,32 +7,33 @@
|
|||||||
<BreakpointProxy
|
<BreakpointProxy
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
<BreakpointContent
|
<BreakpointContent
|
||||||
uuid = "1F3099D0-0456-4AD5-8EA1-52BABAF2AA89"
|
uuid = "499F5405-B63B-4623-9332-1E44FC449FD0"
|
||||||
shouldBeEnabled = "Yes"
|
shouldBeEnabled = "No"
|
||||||
nameForDebugger = "Ignore-SIGUSR"
|
|
||||||
ignoreCount = "0"
|
ignoreCount = "0"
|
||||||
continueAfterRunningActions = "Yes"
|
continueAfterRunningActions = "No"
|
||||||
filePath = "MeloNX/MeloNXApp.swift"
|
filePath = "MeloNX/Views/GamesList/GameListView.swift"
|
||||||
startingColumnNumber = "9223372036854775807"
|
startingColumnNumber = "9223372036854775807"
|
||||||
endingColumnNumber = "9223372036854775807"
|
endingColumnNumber = "9223372036854775807"
|
||||||
startingLineNumber = "14"
|
startingLineNumber = "309"
|
||||||
endingLineNumber = "14"
|
endingLineNumber = "309"
|
||||||
landmarkName = "body"
|
landmarkName = "loadGames()"
|
||||||
landmarkType = "24">
|
landmarkType = "7">
|
||||||
<Actions>
|
</BreakpointContent>
|
||||||
<BreakpointActionProxy
|
</BreakpointProxy>
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
<BreakpointProxy
|
||||||
<ActionContent
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
consoleCommand = "process handle SIGUSR1 -s false -n false">
|
<BreakpointContent
|
||||||
</ActionContent>
|
uuid = "0BB7C122-8933-48E8-ABA3-1ABB39594258"
|
||||||
</BreakpointActionProxy>
|
shouldBeEnabled = "No"
|
||||||
<BreakpointActionProxy
|
ignoreCount = "0"
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
continueAfterRunningActions = "No"
|
||||||
<ActionContent
|
filePath = "MeloNX/Models/Game.swift"
|
||||||
consoleCommand = "process handle SIGBUS -s false -n false">
|
startingColumnNumber = "9223372036854775807"
|
||||||
</ActionContent>
|
endingColumnNumber = "9223372036854775807"
|
||||||
</BreakpointActionProxy>
|
startingLineNumber = "37"
|
||||||
</Actions>
|
endingLineNumber = "37"
|
||||||
|
landmarkName = "createImage(from:)"
|
||||||
|
landmarkType = "7">
|
||||||
</BreakpointContent>
|
</BreakpointContent>
|
||||||
</BreakpointProxy>
|
</BreakpointProxy>
|
||||||
</Breakpoints>
|
</Breakpoints>
|
||||||
|
@ -9,6 +9,16 @@
|
|||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<?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>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
53
src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h
Normal file
53
src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Ryujinx-Header.h
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
#define DRM 0
|
||||||
|
#define CS_DEBUGGED 0x10000000
|
||||||
|
|
||||||
|
#ifndef RyujinxHeader
|
||||||
|
#define RyujinxHeader
|
||||||
|
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_syswm.h>
|
||||||
|
#import "utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct GameInfo {
|
||||||
|
long FileSize;
|
||||||
|
char TitleName[512];
|
||||||
|
char TitleId[32];
|
||||||
|
char Developer[256];
|
||||||
|
char Version[16];
|
||||||
|
unsigned char* ImageData;
|
||||||
|
unsigned int ImageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct GameInfo get_game_info(int, char*);
|
||||||
|
|
||||||
|
void install_firmware(const char* inputPtr);
|
||||||
|
|
||||||
|
char* installed_firmware_version();
|
||||||
|
|
||||||
|
void stop_emulation();
|
||||||
|
|
||||||
|
int main_ryujinx_sdl(int argc, char **argv);
|
||||||
|
|
||||||
|
int get_current_fps();
|
||||||
|
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* RyujinxSDL_h */
|
||||||
|
|
25
src/MeloNX/MeloNX/App/Core/JIT/AskForJIT.swift
Normal file
25
src/MeloNX/MeloNX/App/Core/JIT/AskForJIT.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// AskForJIT.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 9/10/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
func askForJIT() {
|
||||||
|
// Check if TrollStore exists by checking the presence of the directory
|
||||||
|
let urlScheme = "apple-magnifier://enable-jit?bundle-id=\(Bundle.main.bundleIdentifier!)"
|
||||||
|
if let launchURL = URL(string: urlScheme) {
|
||||||
|
if UIApplication.shared.canOpenURL(launchURL) {
|
||||||
|
// Open the URL to enable JIT
|
||||||
|
UIApplication.shared.open(launchURL, options: [:], completionHandler: nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
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
|
||||||
|
}
|
78
src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift
Normal file
78
src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// EnableJIT.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 10/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
func enableJITEB() {
|
||||||
|
guard let bundleID = Bundle.main.bundleIdentifier else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let address = URL(string: "http://[fd00::]:9172/launch_app/\(bundleID)")!
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: address) { data, response, error in
|
||||||
|
if error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
showLaunchAppAlert(jsonData: data!, in: UIApplication.shared.windows.last!.rootViewController!)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LaunchApp: Codable {
|
||||||
|
let ok: Bool
|
||||||
|
let error: String?
|
||||||
|
let launching: Bool
|
||||||
|
let position: Int?
|
||||||
|
let mounting: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/MeloNX/MeloNX/App/Core/JIT/utils.h
Normal file
27
src/MeloNX/MeloNX/App/Core/JIT/utils.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#if __has_feature(modules)
|
||||||
|
@import UIKit;
|
||||||
|
@import Foundation;
|
||||||
|
#else
|
||||||
|
#import "UIKit/UIKit.h"
|
||||||
|
#import "Foundation/Foundation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DISPATCH_ASYNC_START dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
#define DISPATCH_ASYNC_CLOSE });
|
||||||
|
|
||||||
|
#define PT_TRACE_ME 0
|
||||||
|
extern int ptrace(int, pid_t, caddr_t, int);
|
||||||
|
|
||||||
|
#define CS_DEBUGGED 0x10000000
|
||||||
|
extern int csops(
|
||||||
|
pid_t pid,
|
||||||
|
unsigned int ops,
|
||||||
|
void *useraddr,
|
||||||
|
size_t usersize
|
||||||
|
);
|
||||||
|
|
||||||
|
extern BOOL getEntitlementValue(NSString *key);
|
||||||
|
extern BOOL isJITEnabled(void);
|
||||||
|
|
||||||
|
#define DLOG(format, ...) ShowAlert(@"DEBUG", [NSString stringWithFormat:@"\n %s [Line %d] \n %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:format, ##__VA_ARGS__]])
|
||||||
|
void ShowAlert(NSString* title, NSString* message, _Bool* showok);
|
82
src/MeloNX/MeloNX/App/Core/JIT/utils.m
Normal file
82
src/MeloNX/MeloNX/App/Core/JIT/utils.m
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#import "utils.h"
|
||||||
|
|
||||||
|
typedef struct __SecTask * SecTaskRef;
|
||||||
|
extern CFTypeRef SecTaskCopyValueForEntitlement(
|
||||||
|
SecTaskRef task,
|
||||||
|
NSString* entitlement,
|
||||||
|
CFErrorRef _Nullable *error
|
||||||
|
)
|
||||||
|
__attribute__((weak_import));
|
||||||
|
|
||||||
|
extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator)
|
||||||
|
__attribute__((weak_import));
|
||||||
|
|
||||||
|
BOOL getEntitlementValue(NSString *key)
|
||||||
|
{
|
||||||
|
if (SecTaskCreateFromSelf == NULL || SecTaskCopyValueForEntitlement == NULL)
|
||||||
|
return NO;
|
||||||
|
SecTaskRef sec_task = SecTaskCreateFromSelf(NULL);
|
||||||
|
if(!sec_task) return NO;
|
||||||
|
CFTypeRef value = SecTaskCopyValueForEntitlement(sec_task, key, nil);
|
||||||
|
if (value != nil)
|
||||||
|
{
|
||||||
|
CFRelease(value);
|
||||||
|
}
|
||||||
|
CFRelease(sec_task);
|
||||||
|
return value != nil && [(__bridge id)value boolValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL isJITEnabled(void)
|
||||||
|
{
|
||||||
|
if (getEntitlementValue(@"dynamic-codesigning"))
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags;
|
||||||
|
csops(getpid(), 0, &flags, sizeof(flags));
|
||||||
|
return (flags & CS_DEBUGGED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowAlert(NSString* title, NSString* message, _Bool* showok)
|
||||||
|
{
|
||||||
|
DISPATCH_ASYNC_START
|
||||||
|
UIWindow* mainWindow = [[UIApplication sharedApplication] windows].lastObject;
|
||||||
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
|
||||||
|
message:message
|
||||||
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
|
if (showok) {
|
||||||
|
[alert addAction:[UIAlertAction actionWithTitle:@"ok!"
|
||||||
|
style:UIAlertActionStyleDefault
|
||||||
|
handler:nil]];
|
||||||
|
}
|
||||||
|
[mainWindow.rootViewController presentViewController:alert
|
||||||
|
animated:true
|
||||||
|
completion:nil];
|
||||||
|
DISPATCH_ASYNC_CLOSE
|
||||||
|
}
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
__attribute__((constructor)) static void entry(int argc, char **argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
|
||||||
|
NSLog(@"Entitlement Does Exist");
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
[defaults setBool:YES forKey:@"increased-memory-limit"];
|
||||||
|
[defaults synchronize]; // Ensure the value is saved immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getEntitlementValue(@"com.apple.developer.kernel.increased-debugging-memory-limit")) {
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
[defaults setBool:YES forKey:@"increased-debugging-memory-limit"];
|
||||||
|
[defaults synchronize]; // Ensure the value is saved immediately
|
||||||
|
}
|
||||||
|
if (getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) {
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
[defaults setBool:YES forKey:@"extended-virtual-addressing"];
|
||||||
|
[defaults synchronize]; // Ensure the value is saved immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
//
|
||||||
|
// VirtualController.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 8/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreHaptics
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VirtualController {
|
||||||
|
private var instanceID: SDL_JoystickID = -1
|
||||||
|
private var controller: OpaquePointer?
|
||||||
|
|
||||||
|
public let controllername = "MeloNX Touch Controller"
|
||||||
|
|
||||||
|
init() {
|
||||||
|
setupVirtualController()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupVirtualController() {
|
||||||
|
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
|
||||||
|
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
|
||||||
|
}
|
||||||
|
|
||||||
|
var joystickDesc = SDL_VirtualJoystickDesc(
|
||||||
|
version: UInt16(SDL_VIRTUAL_JOYSTICK_DESC_VERSION),
|
||||||
|
type: Uint16(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue),
|
||||||
|
naxes: 6,
|
||||||
|
nbuttons: 15,
|
||||||
|
nhats: 1,
|
||||||
|
vendor_id: 0,
|
||||||
|
product_id: 0,
|
||||||
|
padding: 0,
|
||||||
|
button_mask: 0,
|
||||||
|
axis_mask: 0,
|
||||||
|
name: controllername.withCString { $0 },
|
||||||
|
userdata: nil,
|
||||||
|
Update: { userdata in
|
||||||
|
// Update joystick state here
|
||||||
|
},
|
||||||
|
SetPlayerIndex: { userdata, playerIndex in
|
||||||
|
print("Player index set to \(playerIndex)")
|
||||||
|
},
|
||||||
|
Rumble: { userdata, lowFreq, highFreq in
|
||||||
|
print("Rumble with \(lowFreq), \(highFreq)")
|
||||||
|
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
||||||
|
print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
SetLED: { userdata, red, green, blue in
|
||||||
|
print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
SendEffect: { userdata, data, size in
|
||||||
|
print("Effect sent with size \(size)")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
||||||
|
if instanceID < 0 {
|
||||||
|
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a game controller for the virtual joystick
|
||||||
|
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
||||||
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
|
if controller == nil {
|
||||||
|
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func rumble(lowFreq: Float, highFreq: Float) {
|
||||||
|
do {
|
||||||
|
// Low-frequency haptic pattern
|
||||||
|
let lowFreqPattern = try CHHapticPattern(events: [
|
||||||
|
CHHapticEvent(eventType: .hapticTransient, parameters: [
|
||||||
|
CHHapticEventParameter(parameterID: .hapticIntensity, value: lowFreq),
|
||||||
|
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
|
||||||
|
], relativeTime: 0, duration: 0.2)
|
||||||
|
], parameters: [])
|
||||||
|
|
||||||
|
// High-frequency haptic pattern
|
||||||
|
let highFreqPattern = try CHHapticPattern(events: [
|
||||||
|
CHHapticEvent(eventType: .hapticTransient, parameters: [
|
||||||
|
CHHapticEventParameter(parameterID: .hapticIntensity, value: highFreq),
|
||||||
|
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
|
||||||
|
], relativeTime: 0.2, duration: 0.2)
|
||||||
|
], parameters: [])
|
||||||
|
|
||||||
|
// Create and start the haptic engine
|
||||||
|
let engine = try CHHapticEngine()
|
||||||
|
try engine.start()
|
||||||
|
|
||||||
|
// Create and play the low-frequency player
|
||||||
|
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
||||||
|
try lowFreqPlayer.start(atTime: 0)
|
||||||
|
|
||||||
|
// Create and play the high-frequency player after a short delay
|
||||||
|
let highFreqPlayer = try engine.makePlayer(with: highFreqPattern)
|
||||||
|
try highFreqPlayer.start(atTime: 0.2)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print("Error creating haptic patterns: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
|
||||||
|
guard controller != nil else { return }
|
||||||
|
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
||||||
|
SDL_JoystickSetVirtualAxis(joystick, axis.rawValue, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) {
|
||||||
|
let scaleFactor = 32767.0 / 160
|
||||||
|
|
||||||
|
let scaledX = Int16(min(32767.0, max(-32768.0, x * scaleFactor)))
|
||||||
|
let scaledY = Int16(min(32767.0, max(-32768.0, y * scaleFactor)))
|
||||||
|
|
||||||
|
if stick == .right {
|
||||||
|
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
||||||
|
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTY.rawValue))
|
||||||
|
} else { // ThumbstickType.left
|
||||||
|
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTX.rawValue))
|
||||||
|
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTY.rawValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
||||||
|
guard controller != nil else { return }
|
||||||
|
|
||||||
|
print("Button: \(button.rawValue) {state: \(state)}")
|
||||||
|
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
||||||
|
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||||
|
let value: Int = (state == 1) ? 32767 : 0
|
||||||
|
updateAxisValue(value: Sint16(value), forAxis: axis)
|
||||||
|
} else {
|
||||||
|
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
||||||
|
SDL_JoystickSetVirtualButton(joystick, Int32(button.rawValue), state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup() {
|
||||||
|
if let controller = controller {
|
||||||
|
SDL_GameControllerClose(controller)
|
||||||
|
self.controller = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VirtualControllerButton: Int {
|
||||||
|
case B
|
||||||
|
case A
|
||||||
|
case Y
|
||||||
|
case X
|
||||||
|
case back
|
||||||
|
case guide
|
||||||
|
case start
|
||||||
|
case leftStick
|
||||||
|
case rightStick
|
||||||
|
case leftShoulder
|
||||||
|
case rightShoulder
|
||||||
|
case dPadUp
|
||||||
|
case dPadDown
|
||||||
|
case dPadLeft
|
||||||
|
case dPadRight
|
||||||
|
case leftTrigger
|
||||||
|
case rightTrigger
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ThumbstickType: Int {
|
||||||
|
case left
|
||||||
|
case right
|
||||||
|
}
|
104
src/MeloNX/MeloNX/App/Core/Ryujinx/Display/DisplayVisible.swift
Normal file
104
src/MeloNX/MeloNX/App/Core/Ryujinx/Display/DisplayVisible.swift
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// Untitled.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 28/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GameController
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var theWindow: UIWindow? = nil
|
||||||
|
extension UIWindow {
|
||||||
|
// Makes the SDLWindow use the current WindowScene instead of making its own window.
|
||||||
|
// Also waits for the window to append the on-screen controller
|
||||||
|
@objc func wdb_makeKeyAndVisible() {
|
||||||
|
let enabled = UserDefaults.standard.bool(forKey: "oldWindowCode")
|
||||||
|
|
||||||
|
if #unavailable(iOS 17.0), enabled {
|
||||||
|
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wdb_makeKeyAndVisible()
|
||||||
|
theWindow = self
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patches makeKeyAndVisible to wdb_makeKeyAndVisible
|
||||||
|
func patchMakeKeyAndVisible() {
|
||||||
|
let uiwindowClass = UIWindow.self
|
||||||
|
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
|
||||||
|
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
|
||||||
|
method_exchangeImplementations(m1, m2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// FPSMonitor.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class FPSMonitor: ObservableObject {
|
||||||
|
@Published private(set) var currentFPS: UInt64 = 0
|
||||||
|
private var timer: Timer?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateFPS()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateFPS() {
|
||||||
|
let currentfps = UInt64(get_current_fps())
|
||||||
|
|
||||||
|
self.currentFPS = currentfps
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func formatFPS() -> String {
|
||||||
|
let fps = Double(currentFPS)
|
||||||
|
let fpsString = String(format: "FPS: %.2f", fps)
|
||||||
|
|
||||||
|
return fpsString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// MemoryUsageMonitor.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class MemoryUsageMonitor: ObservableObject {
|
||||||
|
@Published private(set) var memoryUsage: UInt64 = 0
|
||||||
|
private var timer: Timer?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateMemoryUsage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateMemoryUsage() {
|
||||||
|
var taskInfo = task_vm_info_data_t()
|
||||||
|
var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size) / 4
|
||||||
|
let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
|
||||||
|
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
|
||||||
|
task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == KERN_SUCCESS {
|
||||||
|
memoryUsage = taskInfo.phys_footprint
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print("Error with task_info(): " +
|
||||||
|
(String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMemorySize(_ bytes: UInt64) -> String {
|
||||||
|
let formatter = ByteCountFormatter()
|
||||||
|
formatter.allowedUnits = [.useMB, .useGB]
|
||||||
|
formatter.countStyle = .memory
|
||||||
|
return formatter.string(fromByteCount: Int64(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// Untitled.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PerformanceOverlayView: View {
|
||||||
|
@StateObject private var memorymonitor = MemoryUsageMonitor()
|
||||||
|
|
||||||
|
@StateObject private var fpsmonitor = FPSMonitor()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("\(fpsmonitor.formatFPS())")
|
||||||
|
Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Screenshot.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 09/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIView {
|
||||||
|
func screenshot() -> UIImage? {
|
||||||
|
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
|
||||||
|
defer { UIGraphicsEndImageContext() }
|
||||||
|
|
||||||
|
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
|
||||||
|
return UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
class MTLHud {
|
class MTLHud {
|
||||||
|
|
||||||
var canMetalHud: Bool {
|
var canMetalHud: Bool {
|
526
src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift
Normal file
526
src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
//
|
||||||
|
// Ryujinx.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
import GameController
|
||||||
|
|
||||||
|
struct Controller: Identifiable, Hashable {
|
||||||
|
var id: String
|
||||||
|
var name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct iOSNav<Content: View>: View {
|
||||||
|
@ViewBuilder var content: () -> Content
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
NavigationStack(root: content)
|
||||||
|
} else {
|
||||||
|
NavigationView(content: content)
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
.navigationViewStyle(.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AspectRatio: String, Codable, CaseIterable {
|
||||||
|
case fixed4x3 = "Fixed4x3"
|
||||||
|
case fixed16x9 = "Fixed16x9"
|
||||||
|
case fixed16x10 = "Fixed16x10"
|
||||||
|
case fixed21x9 = "Fixed21x9"
|
||||||
|
case fixed32x9 = "Fixed32x9"
|
||||||
|
case stretched = "Stretched"
|
||||||
|
|
||||||
|
var displayName: String {
|
||||||
|
switch self {
|
||||||
|
case .fixed4x3: return "4:3"
|
||||||
|
case .fixed16x9: return "16:9 (Default)"
|
||||||
|
case .fixed16x10: return "16:10"
|
||||||
|
case .fixed21x9: return "21:9"
|
||||||
|
case .fixed32x9: return "32:9"
|
||||||
|
case .stretched: return "Stretched (Full Screen)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Ryujinx {
|
||||||
|
private var isRunning = false
|
||||||
|
|
||||||
|
let virtualController = VirtualController()
|
||||||
|
|
||||||
|
@Published var controllerMap: [Controller] = []
|
||||||
|
@Published var metalLayer: CAMetalLayer? = nil
|
||||||
|
@Published var firmwareversion = "0"
|
||||||
|
@Published var emulationUIView = UIView()
|
||||||
|
@Published var games: [Game] = []
|
||||||
|
|
||||||
|
var shouldMetal: Bool {
|
||||||
|
metalLayer == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
static let shared = Ryujinx()
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
self.games = loadGames()
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Configuration : Codable, Equatable {
|
||||||
|
var gamepath: String
|
||||||
|
var inputids: [String]
|
||||||
|
var resscale: Float
|
||||||
|
var debuglogs: Bool
|
||||||
|
var tracelogs: Bool
|
||||||
|
var nintendoinput: Bool
|
||||||
|
var enableInternet: Bool
|
||||||
|
var listinputids: Bool
|
||||||
|
var aspectRatio: AspectRatio
|
||||||
|
var memoryManagerMode: String
|
||||||
|
var disableShaderCache: Bool
|
||||||
|
var hypervisor: Bool
|
||||||
|
var disableDockedMode: Bool
|
||||||
|
var enableTextureRecompression: Bool
|
||||||
|
var additionalArgs: [String]
|
||||||
|
var maxAnisotropy: Float
|
||||||
|
var macroHLE: Bool
|
||||||
|
var ignoreMissingServices: Bool
|
||||||
|
var expandRam: Bool
|
||||||
|
var dfsIntegrityChecks: Bool
|
||||||
|
var disablePTC: Bool
|
||||||
|
var disablevsync: Bool
|
||||||
|
|
||||||
|
|
||||||
|
init(gamepath: String,
|
||||||
|
inputids: [String] = [],
|
||||||
|
debuglogs: Bool = false,
|
||||||
|
tracelogs: Bool = false,
|
||||||
|
listinputids: Bool = false,
|
||||||
|
aspectRatio: AspectRatio = .fixed16x9,
|
||||||
|
memoryManagerMode: String = "HostMappedUnsafe",
|
||||||
|
disableShaderCache: Bool = false,
|
||||||
|
disableDockedMode: Bool = false,
|
||||||
|
nintendoinput: Bool = true,
|
||||||
|
enableInternet: Bool = false,
|
||||||
|
enableTextureRecompression: Bool = true,
|
||||||
|
additionalArgs: [String] = [],
|
||||||
|
resscale: Float = 1.00,
|
||||||
|
maxAnisotropy: Float = 0,
|
||||||
|
macroHLE: Bool = false,
|
||||||
|
ignoreMissingServices: Bool = false,
|
||||||
|
hypervisor: Bool = false,
|
||||||
|
expandRam: Bool = false,
|
||||||
|
dfsIntegrityChecks: Bool = false,
|
||||||
|
disablePTC: Bool = false,
|
||||||
|
disablevsync: Bool = false
|
||||||
|
) {
|
||||||
|
self.gamepath = gamepath
|
||||||
|
self.inputids = inputids
|
||||||
|
self.debuglogs = debuglogs
|
||||||
|
self.tracelogs = tracelogs
|
||||||
|
self.listinputids = listinputids
|
||||||
|
self.aspectRatio = aspectRatio
|
||||||
|
self.disableShaderCache = disableShaderCache
|
||||||
|
self.disableDockedMode = disableDockedMode
|
||||||
|
self.enableTextureRecompression = enableTextureRecompression
|
||||||
|
self.additionalArgs = additionalArgs
|
||||||
|
self.memoryManagerMode = memoryManagerMode
|
||||||
|
self.resscale = resscale
|
||||||
|
self.nintendoinput = nintendoinput
|
||||||
|
self.enableInternet = enableInternet
|
||||||
|
self.maxAnisotropy = maxAnisotropy
|
||||||
|
self.macroHLE = macroHLE
|
||||||
|
self.expandRam = expandRam
|
||||||
|
self.ignoreMissingServices = ignoreMissingServices
|
||||||
|
self.hypervisor = hypervisor
|
||||||
|
self.dfsIntegrityChecks = dfsIntegrityChecks
|
||||||
|
self.disablePTC = disablePTC
|
||||||
|
self.disablevsync = disablevsync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func start(with config: Configuration) throws {
|
||||||
|
guard !isRunning else {
|
||||||
|
throw RyujinxError.alreadyRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning = true
|
||||||
|
|
||||||
|
RunLoop.current.perform {
|
||||||
|
|
||||||
|
let url = URL(string: config.gamepath)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let args = self.buildCommandLineArgs(from: config)
|
||||||
|
let accessing = url?.startAccessingSecurityScopedResource()
|
||||||
|
|
||||||
|
// Convert Arguments to ones that Ryujinx can Read
|
||||||
|
let cArgs = args.map { strdup($0) }
|
||||||
|
defer { cArgs.forEach { free($0) } }
|
||||||
|
var argvPtrs = cArgs
|
||||||
|
|
||||||
|
// Start the emulation
|
||||||
|
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
||||||
|
|
||||||
|
if result != 0 {
|
||||||
|
self.isRunning = false
|
||||||
|
if let accessing, accessing {
|
||||||
|
url!.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw RyujinxError.executionError(code: result)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.isRunning = false
|
||||||
|
Self.log("Emulation failed to start: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func stop() throws {
|
||||||
|
guard isRunning else {
|
||||||
|
throw RyujinxError.notRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var running: Bool {
|
||||||
|
return isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func loadGames() -> [Game] {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return [] }
|
||||||
|
|
||||||
|
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
||||||
|
|
||||||
|
if (!fileManager.fileExists(atPath: romsDirectory.path)) {
|
||||||
|
do {
|
||||||
|
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
} catch {
|
||||||
|
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] {
|
||||||
|
var args: [String] = []
|
||||||
|
|
||||||
|
// Add the game path
|
||||||
|
args.append(config.gamepath)
|
||||||
|
|
||||||
|
// Starts with vulkan
|
||||||
|
args.append("--graphics-backend")
|
||||||
|
args.append("Vulkan")
|
||||||
|
|
||||||
|
args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode])
|
||||||
|
|
||||||
|
// args.append(contentsOf: ["--exclusive-fullscreen", String(true)])
|
||||||
|
// args.append(contentsOf: ["--exclusive-fullscreen-width", "\(Int(UIScreen.main.bounds.width))"])
|
||||||
|
// args.append(contentsOf: ["--exclusive-fullscreen-height", "\(Int(UIScreen.main.bounds.height))"])
|
||||||
|
// We don't need this. Ryujinx should handle it fine :3
|
||||||
|
// this also causes crashes in some games :3
|
||||||
|
|
||||||
|
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
|
||||||
|
|
||||||
|
if config.nintendoinput {
|
||||||
|
args.append("--correct-controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.disablePTC {
|
||||||
|
args.append("--disable-ptc")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.disablevsync {
|
||||||
|
args.append("--disable-vsync")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if config.hypervisor {
|
||||||
|
args.append("--use-hypervisor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.dfsIntegrityChecks {
|
||||||
|
args.append("--disable-fs-integrity-checks")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if config.resscale != 1.0 {
|
||||||
|
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.expandRam {
|
||||||
|
args.append(contentsOf: ["--expand-ram", String(config.expandRam)])
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.ignoreMissingServices {
|
||||||
|
args.append(contentsOf: ["--ignore-missing-services", String(config.maxAnisotropy)])
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.maxAnisotropy != 0 {
|
||||||
|
args.append(contentsOf: ["--max-anisotropy", String(config.maxAnisotropy)])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.macroHLE {
|
||||||
|
args.append("--disable-macro-hle")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.disableShaderCache { // same with disableShaderCache
|
||||||
|
args.append("--disable-shader-cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.disableDockedMode { // disableDockedMode is actually enableDockedMode, i just have flipped it around in the settings page to make it easier to understand :3
|
||||||
|
args.append("--disable-docked-mode")
|
||||||
|
}
|
||||||
|
if config.enableTextureRecompression {
|
||||||
|
args.append("--enable-texture-recompression")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.debuglogs {
|
||||||
|
args.append(contentsOf: ["--enable-debug-logs"])
|
||||||
|
}
|
||||||
|
if config.tracelogs {
|
||||||
|
args.append(contentsOf: ["--enable-trace-logs"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// List the input ids
|
||||||
|
if config.listinputids {
|
||||||
|
args.append(contentsOf: ["--list-inputs-ids"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the input ids (limit to 4 just in case)
|
||||||
|
if !config.inputids.isEmpty {
|
||||||
|
config.inputids.prefix(4).enumerated().forEach { index, inputId in
|
||||||
|
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apped any additional arguments
|
||||||
|
args.append(contentsOf: config.additionalArgs)
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchFirmwareVersion() -> String {
|
||||||
|
do {
|
||||||
|
let firmwareVersionPointer = installed_firmware_version()
|
||||||
|
if let pointer = firmwareVersionPointer {
|
||||||
|
let firmwareVersion = String(cString: pointer)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.firmwareversion = firmwareVersion
|
||||||
|
}
|
||||||
|
return firmwareVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func installFirmware(firmwarePath: String) {
|
||||||
|
guard let cString = firmwarePath.cString(using: .utf8) else {
|
||||||
|
print("Invalid firmware path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
install_firmware(cString)
|
||||||
|
|
||||||
|
let version = fetchFirmwareVersion()
|
||||||
|
if !version.isEmpty {
|
||||||
|
self.firmwareversion = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
||||||
|
let guid = SDL_JoystickGetDeviceGUID(joystickIndex)
|
||||||
|
|
||||||
|
if guid.data.0 == 0 && guid.data.1 == 0 && guid.data.2 == 0 && guid.data.3 == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let reorderedGUID: [UInt8] = [
|
||||||
|
guid.data.3, guid.data.2, guid.data.1, guid.data.0,
|
||||||
|
guid.data.5, guid.data.4,
|
||||||
|
guid.data.7, guid.data.6,
|
||||||
|
guid.data.8, guid.data.9,
|
||||||
|
guid.data.10, guid.data.11, guid.data.12, guid.data.13, guid.data.14, guid.data.15
|
||||||
|
]
|
||||||
|
|
||||||
|
let guidString = reorderedGUID.map { String(format: "%02X", $0) }.joined().lowercased()
|
||||||
|
|
||||||
|
func substring(_ str: String, _ start: Int, _ end: Int) -> String {
|
||||||
|
let startIdx = str.index(str.startIndex, offsetBy: start)
|
||||||
|
let endIdx = str.index(str.startIndex, offsetBy: end)
|
||||||
|
return String(str[startIdx..<endIdx])
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedGUID = "\(substring(guidString, 0, 8))-\(substring(guidString, 8, 12))-\(substring(guidString, 12, 16))-\(substring(guidString, 16, 20))-\(substring(guidString, 20, 32))"
|
||||||
|
|
||||||
|
return "\(joystickIndex)-\(formattedGUID)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnectedControllers() -> [Controller] {
|
||||||
|
var controllers: [Controller] = []
|
||||||
|
|
||||||
|
let numJoysticks = SDL_NumJoysticks()
|
||||||
|
|
||||||
|
for i in 0..<numJoysticks {
|
||||||
|
if let controller = SDL_GameControllerOpen(i) {
|
||||||
|
let guid = generateGamepadId(joystickIndex: i)
|
||||||
|
let name = String(cString: SDL_GameControllerName(controller))
|
||||||
|
|
||||||
|
print("Controller \(i): \(name), GUID: \(guid ?? "")")
|
||||||
|
|
||||||
|
guard let guid else {
|
||||||
|
SDL_GameControllerClose(controller)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
controllers.append(Controller(id: guid, name: name))
|
||||||
|
|
||||||
|
SDL_GameControllerClose(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return controllers
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFirmware() {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
|
||||||
|
let documentsfolder = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
|
||||||
|
|
||||||
|
let bisFolder = documentsfolder.appendingPathComponent("bis")
|
||||||
|
let systemFolder = bisFolder.appendingPathComponent("system")
|
||||||
|
let contentsFolder = systemFolder.appendingPathComponent("Contents")
|
||||||
|
let registeredFolder = contentsFolder.appendingPathComponent("registered").path
|
||||||
|
|
||||||
|
|
||||||
|
do {
|
||||||
|
if fileManager.fileExists(atPath: registeredFolder) {
|
||||||
|
try fileManager.removeItem(atPath: registeredFolder)
|
||||||
|
print("Folder removed successfully.")
|
||||||
|
let version = fetchFirmwareVersion()
|
||||||
|
|
||||||
|
if version.isEmpty {
|
||||||
|
self.firmwareversion = "0"
|
||||||
|
} else {
|
||||||
|
print("Firmware eeeeee \(version)")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("Folder does not exist.")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error removing folder: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func repeatuntilfindLayer() {
|
||||||
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
while self.metalLayer == nil {
|
||||||
|
let layer = self.getMetalLayer(nil)
|
||||||
|
|
||||||
|
if layer != nil {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.metalLayer = layer
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(forTimeInterval: 0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
|
||||||
|
var window = window
|
||||||
|
if window == nil {
|
||||||
|
window = SDL_GetWindowFromID(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var windowInfo = SDL_SysWMinfo()
|
||||||
|
SDL_GetWindowWMInfo(window, &windowInfo)
|
||||||
|
|
||||||
|
|
||||||
|
guard let uiWindow = windowInfo.info.uikit.window,
|
||||||
|
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
|
||||||
|
print("Unable to get root view")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMetalLayer(in view: UIView) -> CAMetalLayer? {
|
||||||
|
if let metalLayer = view.layer as? CAMetalLayer {
|
||||||
|
return metalLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
for subview in view.subviews {
|
||||||
|
if let metalLayer = findMetalLayer(in: subview) {
|
||||||
|
return metalLayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let existingLayer = findMetalLayer(in: rootView) {
|
||||||
|
print("Found Metal Layer")
|
||||||
|
return existingLayer
|
||||||
|
}
|
||||||
|
print("found nothing")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static func log(_ message: String) {
|
||||||
|
print("[Ryujinx] \(message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
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 }
|
||||||
|
}
|
||||||
|
}
|
84
src/MeloNX/MeloNX/App/Models/Game.swift
Normal file
84
src/MeloNX/MeloNX/App/Models/Game.swift
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// GameInfo.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 9/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
public struct Game: Identifiable, Equatable, Hashable {
|
||||||
|
public var id = UUID()
|
||||||
|
|
||||||
|
var containerFolder: URL
|
||||||
|
var fileType: UTType
|
||||||
|
var fileURL: URL
|
||||||
|
|
||||||
|
var titleName: String
|
||||||
|
var titleId: String
|
||||||
|
var developer: String
|
||||||
|
var version: String
|
||||||
|
var icon: UIImage?
|
||||||
|
|
||||||
|
|
||||||
|
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game {
|
||||||
|
var gameInfo = gameInfo
|
||||||
|
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
|
||||||
|
|
||||||
|
gameTemp.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
|
||||||
|
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
|
||||||
|
String(cString: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameTemp.developer = withUnsafePointer(to: &gameInfo.Developer) {
|
||||||
|
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
|
||||||
|
String(cString: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameTemp.titleId = withUnsafePointer(to: &gameInfo.TitleId) {
|
||||||
|
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
|
||||||
|
String(cString: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gameTemp.version = withUnsafePointer(to: &gameInfo.Version) {
|
||||||
|
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
|
||||||
|
String(cString: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageSize = Int(gameInfo.ImageSize)
|
||||||
|
if imageSize > 0, imageSize <= 1024 * 1024 {
|
||||||
|
let imageData = Data(bytes: gameInfo.ImageData, count: imageSize)
|
||||||
|
|
||||||
|
gameTemp.icon = UIImage(data: imageData)
|
||||||
|
} else {
|
||||||
|
print("Invalid image size.")
|
||||||
|
}
|
||||||
|
return gameTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
||||||
|
// Access the struct
|
||||||
|
let gameInfoValue = gameInfo
|
||||||
|
|
||||||
|
// Get the image data
|
||||||
|
let imageSize = Int(gameInfoValue.ImageSize)
|
||||||
|
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
||||||
|
print("Invalid image size.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the ImageData byte array to Swift's Data
|
||||||
|
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
||||||
|
|
||||||
|
// Create a UIImage (or NSImage on macOS)
|
||||||
|
print(imageData)
|
||||||
|
|
||||||
|
return UIImage(data: imageData)
|
||||||
|
}
|
||||||
|
}
|
399
src/MeloNX/MeloNX/App/Views/ContentView.swift
Normal file
399
src/MeloNX/MeloNX/App/Views/ContentView.swift
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
//
|
||||||
|
// ContentView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
// import SDL2
|
||||||
|
import GameController
|
||||||
|
import Darwin
|
||||||
|
import UIKit
|
||||||
|
import MetalKit
|
||||||
|
// import SDL
|
||||||
|
|
||||||
|
struct MoltenVKSettings: Codable, Hashable {
|
||||||
|
let string: String
|
||||||
|
var value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
// Games
|
||||||
|
@State private var game: Game?
|
||||||
|
|
||||||
|
// Controllers
|
||||||
|
@State private var controllersList: [Controller] = []
|
||||||
|
@State private var currentControllers: [Controller] = []
|
||||||
|
@State var onscreencontroller: Controller = Controller(id: "", name: "")
|
||||||
|
@State private var isVirtualControllerActive: Bool = false
|
||||||
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
|
|
||||||
|
// Settings and Configuration
|
||||||
|
@State private var config: Ryujinx.Configuration
|
||||||
|
@State var settings: [MoltenVKSettings]
|
||||||
|
@AppStorage("useTrollStore") var useTrollStore: Bool = false
|
||||||
|
|
||||||
|
// JIT
|
||||||
|
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
|
||||||
|
|
||||||
|
// Other Configuration
|
||||||
|
@State var isMK8: Bool = false
|
||||||
|
@AppStorage("quit") var quit: Bool = false
|
||||||
|
@State var quits: Bool = false
|
||||||
|
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
||||||
|
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
|
||||||
|
|
||||||
|
// Loading Animation
|
||||||
|
@State private var clumpOffset: CGFloat = -100
|
||||||
|
private let clumpWidth: CGFloat = 100
|
||||||
|
private let animationDuration: Double = 1.0
|
||||||
|
@State private var isAnimating = false
|
||||||
|
@State var isLoading = true
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
init() {
|
||||||
|
let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "")
|
||||||
|
_config = State(initialValue: defaultConfig)
|
||||||
|
|
||||||
|
let defaultSettings: [MoltenVKSettings] = [
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2"),
|
||||||
|
// Metal Private API isn't needed and causes more stutters
|
||||||
|
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
|
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
|
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
||||||
|
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "0"),
|
||||||
|
// MVK_CONFIG_LOG_LEVEL
|
||||||
|
//MVK_DEBUG
|
||||||
|
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64 or 192 depending on what i decide)
|
||||||
|
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
|
||||||
|
]
|
||||||
|
|
||||||
|
_settings = State(initialValue: defaultSettings)
|
||||||
|
|
||||||
|
print("JIT Enabled: \(isJITEnabled())")
|
||||||
|
|
||||||
|
initializeSDL()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
var body: some View {
|
||||||
|
if game != nil, quits == false {
|
||||||
|
if isLoading {
|
||||||
|
if Air.shared.connected {
|
||||||
|
Text("")
|
||||||
|
.onAppear() {
|
||||||
|
Air.play(AnyView(emulationView))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
emulationView
|
||||||
|
.onAppear() {
|
||||||
|
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
|
||||||
|
/*
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||||
|
timer.invalidate()
|
||||||
|
quits = quit
|
||||||
|
|
||||||
|
if quits {
|
||||||
|
quit = false
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is when the game starts to stop the animation
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
EmulationView()
|
||||||
|
.persistentSystemOverlays(.hidden)
|
||||||
|
.onAppear() {
|
||||||
|
isAnimating = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
VStack {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is the main menu view that includes the Settings and the Game Selector
|
||||||
|
mainMenuView
|
||||||
|
.onAppear() {
|
||||||
|
quits = false
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func initControllerObservers() {
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
forName: .GCControllerDidConnect,
|
||||||
|
object: nil,
|
||||||
|
queue: .main) { notification in
|
||||||
|
if let controller = notification.object as? GCController {
|
||||||
|
print("Controller connected: \(controller.productCategory)")
|
||||||
|
refreshControllersList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
forName: .GCControllerDidDisconnect,
|
||||||
|
object: nil,
|
||||||
|
queue: .main) { notification in
|
||||||
|
if let controller = notification.object as? GCController {
|
||||||
|
print("Controller disconnected: \(controller.productCategory)")
|
||||||
|
refreshControllersList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Components
|
||||||
|
private var emulationView: some View {
|
||||||
|
GeometryReader { screenGeometry in
|
||||||
|
ZStack {
|
||||||
|
HStack(spacing: screenGeometry.size.width * 0.04) {
|
||||||
|
if let icon = game?.icon {
|
||||||
|
Image(uiImage: icon)
|
||||||
|
.resizable()
|
||||||
|
.frame(
|
||||||
|
width: min(screenGeometry.size.width * 0.25, 250),
|
||||||
|
height: min(screenGeometry.size.width * 0.25, 250)
|
||||||
|
)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
.shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: screenGeometry.size.height * 0.015) {
|
||||||
|
Text("Loading \(game?.titleName ?? "Game")")
|
||||||
|
.font(.system(size: min(screenGeometry.size.width * 0.04, 32)))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
GeometryReader { geometry in
|
||||||
|
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
|
||||||
|
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
// Background track
|
||||||
|
Rectangle()
|
||||||
|
.cornerRadius(10)
|
||||||
|
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
|
||||||
|
.foregroundColor(.gray.opacity(0.3))
|
||||||
|
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
|
||||||
|
|
||||||
|
// Animated loading bar
|
||||||
|
Rectangle()
|
||||||
|
.cornerRadius(10)
|
||||||
|
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.shadow(color: .blue.opacity(0.5), radius: 4, x: 0, y: 2)
|
||||||
|
.offset(x: isAnimating ? containerWidth : -clumpWidth)
|
||||||
|
.animation(
|
||||||
|
Animation.linear(duration: 1.0)
|
||||||
|
.repeatForever(autoreverses: false),
|
||||||
|
value: isAnimating
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
.onAppear {
|
||||||
|
isAnimating = true
|
||||||
|
|
||||||
|
setupEmulation()
|
||||||
|
|
||||||
|
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
||||||
|
if get_current_fps() != 0 {
|
||||||
|
withAnimation {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isAnimating = false
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
print(get_current_fps())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: min(screenGeometry.size.height * 0.015, 12))
|
||||||
|
.frame(width: min(screenGeometry.size.width * 0.35, 350))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, screenGeometry.size.width * 0.06)
|
||||||
|
.padding(.vertical, screenGeometry.size.height * 0.05)
|
||||||
|
.position(
|
||||||
|
x: screenGeometry.size.width / 2,
|
||||||
|
y: screenGeometry.size.height * 0.5
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mainMenuView: some View {
|
||||||
|
MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
|
||||||
|
.onAppear() {
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { timer in
|
||||||
|
refreshControllersList()
|
||||||
|
}
|
||||||
|
|
||||||
|
Air.play(AnyView(
|
||||||
|
VStack {
|
||||||
|
Image(systemName: "gamecontroller")
|
||||||
|
.font(.system(size: 300))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
Text("Select Game")
|
||||||
|
.font(.system(size: 150))
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
let isJIT = isJITEnabled()
|
||||||
|
|
||||||
|
if !isJIT, useTrollStore {
|
||||||
|
askForJIT()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isJIT, jitStreamerEB {
|
||||||
|
enableJITEB()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helper Methods
|
||||||
|
var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; // Initialises SDL2 for Events, Game Controller, Joystick, Audio and Video.
|
||||||
|
private func initializeSDL() {
|
||||||
|
setMoltenVKSettings()
|
||||||
|
SDL_SetMainReady() // Sets SDL Ready
|
||||||
|
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true (Check out SDL2 Documentation here)
|
||||||
|
SDL_Init(SdlInitFlags) // Initialises SDL2
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupEmulation() {
|
||||||
|
patchMakeKeyAndVisible()
|
||||||
|
isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
start(displayid: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func refreshControllersList() {
|
||||||
|
controllersList = Ryujinx.shared.getConnectedControllers()
|
||||||
|
|
||||||
|
if let onscreen = controllersList.first(where: { $0.name == Ryujinx.shared.virtualController.controllername }) {
|
||||||
|
self.onscreencontroller = onscreen
|
||||||
|
}
|
||||||
|
|
||||||
|
controllersList.removeAll(where: { $0.id == "0"})
|
||||||
|
|
||||||
|
currentControllers = []
|
||||||
|
|
||||||
|
if controllersList.count == 1 {
|
||||||
|
let controller = controllersList[0]
|
||||||
|
currentControllers.append(controller)
|
||||||
|
} else if (controllersList.count - 1) >= 1 {
|
||||||
|
for controller in controllersList {
|
||||||
|
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
||||||
|
currentControllers.append(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func showAlert(title: String, message: String, showOk: Bool, completion: @escaping (Bool) -> Void) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let mainWindow = UIApplication.shared.windows.last {
|
||||||
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
|
|
||||||
|
if showOk {
|
||||||
|
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
alert.addAction(okAction)
|
||||||
|
} else {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.rootViewController?.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func start(displayid: UInt32) {
|
||||||
|
guard let game else { return }
|
||||||
|
|
||||||
|
config.gamepath = game.fileURL.path
|
||||||
|
config.inputids = Array(Set(currentControllers.map(\.id)))
|
||||||
|
|
||||||
|
if mVKPreFillBuffer {
|
||||||
|
let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2")
|
||||||
|
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 {
|
||||||
|
config.inputids.append("0")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try Ryujinx.shared.start(with: config)
|
||||||
|
} catch {
|
||||||
|
print("Error: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sets MoltenVK Environment Variables
|
||||||
|
private func setMoltenVKSettings() {
|
||||||
|
settings.forEach { setting in
|
||||||
|
setenv(setting.string, setting.value, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helper Functions
|
||||||
|
func loadSettings() -> Ryujinx.Configuration? {
|
||||||
|
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
|
||||||
|
let data = jsonString.data(using: .utf8) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
|
||||||
|
} catch {
|
||||||
|
print("Failed to load settings: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
303
src/MeloNX/MeloNX/App/Views/ControllerView/ControllerView.swift
Normal file
303
src/MeloNX/MeloNX/App/Views/ControllerView/ControllerView.swift
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
//
|
||||||
|
// ControllerView.swift
|
||||||
|
// Pomelo-V2
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 16/7/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import GameController
|
||||||
|
import SwiftUIJoystick
|
||||||
|
import CoreMotion
|
||||||
|
|
||||||
|
struct ControllerView: View {
|
||||||
|
|
||||||
|
@AppStorage("performacehud") var performacehud: Bool = false
|
||||||
|
@AppStorage("quit") var quit: Bool = false
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
||||||
|
VStack {
|
||||||
|
if performacehud {
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
PerformanceOverlayView()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// Button("Stop emulation") {
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// stop_emulation()
|
||||||
|
// quit = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewLeft()
|
||||||
|
ZStack {
|
||||||
|
Joystick()
|
||||||
|
DPadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewRight()
|
||||||
|
ZStack {
|
||||||
|
Joystick(iscool: true) // hope this works
|
||||||
|
ABXYView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .start) // Adding the + button
|
||||||
|
.padding(.horizontal, 40)
|
||||||
|
ButtonView(button: .back) // Adding the - button
|
||||||
|
.padding(.horizontal, 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// could be landscape
|
||||||
|
VStack {
|
||||||
|
if performacehud {
|
||||||
|
HStack {
|
||||||
|
PerformanceOverlayView()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// Button("Stop emulation") {
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// stop_emulation()
|
||||||
|
// quit = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
// gotta fuckin add + and - now
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewLeft()
|
||||||
|
ZStack {
|
||||||
|
Joystick()
|
||||||
|
DPadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
// Spacer()
|
||||||
|
VStack {
|
||||||
|
// Spacer()
|
||||||
|
ButtonView(button: .back) // Adding the - button
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
// Spacer()
|
||||||
|
ButtonView(button: .start) // Adding the + button
|
||||||
|
}
|
||||||
|
// Spacer()
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
ShoulderButtonsViewRight()
|
||||||
|
ZStack {
|
||||||
|
Joystick(iscool: true) // hope this work s
|
||||||
|
ABXYView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// .padding(.bottom, geometry.size.height / 11) // also extremally broken (
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShoulderButtonsViewLeft: View {
|
||||||
|
@State var width: CGFloat = 160
|
||||||
|
@State var height: CGFloat = 20
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .leftTrigger)
|
||||||
|
.padding(.horizontal)
|
||||||
|
ButtonView(button: .leftShoulder)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
width *= 1.2
|
||||||
|
height *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShoulderButtonsViewRight: View {
|
||||||
|
@State var width: CGFloat = 160
|
||||||
|
@State var height: CGFloat = 20
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .rightShoulder)
|
||||||
|
.padding(.horizontal)
|
||||||
|
ButtonView(button: .rightTrigger)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
width *= 1.2
|
||||||
|
height *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DPadView: View {
|
||||||
|
@State var size: CGFloat = 145
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ButtonView(button: .dPadUp)
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .dPadLeft)
|
||||||
|
Spacer(minLength: 20)
|
||||||
|
ButtonView(button: .dPadRight)
|
||||||
|
}
|
||||||
|
ButtonView(button: .dPadDown)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
size *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ABXYView: View {
|
||||||
|
@State var size: CGFloat = 145
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
ButtonView(button: .X)
|
||||||
|
HStack {
|
||||||
|
ButtonView(button: .Y)
|
||||||
|
Spacer(minLength: 20)
|
||||||
|
ButtonView(button: .A)
|
||||||
|
}
|
||||||
|
ButtonView(button: .B)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.onAppear() {
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
size *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ButtonView: View {
|
||||||
|
var button: VirtualControllerButton
|
||||||
|
@State var width: CGFloat = 45
|
||||||
|
@State var height: CGFloat = 45
|
||||||
|
@State var isPressed = false
|
||||||
|
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Image(systemName: buttonText)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: width, height: height)
|
||||||
|
.foregroundColor(colorScheme == .dark ? Color.gray : Color.gray)
|
||||||
|
.opacity(isPressed ? 0.4 : 0.7)
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { _ in
|
||||||
|
if !self.isPressed {
|
||||||
|
self.isPressed = true
|
||||||
|
Ryujinx.shared.virtualController.setButtonState(1, for: button)
|
||||||
|
Haptics.shared.play(.heavy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEnded { _ in
|
||||||
|
self.isPressed = false
|
||||||
|
Ryujinx.shared.virtualController.setButtonState(0, for: button)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onAppear() {
|
||||||
|
if button == .leftTrigger || button == .rightTrigger || button == .leftShoulder || button == .rightShoulder {
|
||||||
|
width = 65
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if button == .back || button == .start || button == .guide {
|
||||||
|
width = 35
|
||||||
|
height = 35
|
||||||
|
}
|
||||||
|
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
width *= 1.2
|
||||||
|
height *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private var buttonText: String {
|
||||||
|
switch button {
|
||||||
|
case .A:
|
||||||
|
return "a.circle.fill"
|
||||||
|
case .B:
|
||||||
|
return "b.circle.fill"
|
||||||
|
case .X:
|
||||||
|
return "x.circle.fill"
|
||||||
|
case .Y:
|
||||||
|
return "y.circle.fill"
|
||||||
|
case .dPadUp:
|
||||||
|
return "arrowtriangle.up.circle.fill"
|
||||||
|
case .dPadDown:
|
||||||
|
return "arrowtriangle.down.circle.fill"
|
||||||
|
case .dPadLeft:
|
||||||
|
return "arrowtriangle.left.circle.fill"
|
||||||
|
case .dPadRight:
|
||||||
|
return "arrowtriangle.right.circle.fill"
|
||||||
|
case .leftTrigger:
|
||||||
|
return"zl.rectangle.roundedtop.fill"
|
||||||
|
case .rightTrigger:
|
||||||
|
return "zr.rectangle.roundedtop.fill"
|
||||||
|
case .leftShoulder:
|
||||||
|
return "l.rectangle.roundedbottom.fill"
|
||||||
|
case .rightShoulder:
|
||||||
|
return "r.rectangle.roundedbottom.fill"
|
||||||
|
case .start:
|
||||||
|
return "plus.circle.fill" // System symbol for +
|
||||||
|
case .back:
|
||||||
|
return "minus.circle.fill" // System symbol for -
|
||||||
|
case .guide:
|
||||||
|
return "house.circle.fill"
|
||||||
|
// This should be all the cases
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Haptics.swift
|
||||||
|
// Pomelo
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 11/9/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class Haptics {
|
||||||
|
static let shared = Haptics()
|
||||||
|
|
||||||
|
private init() { }
|
||||||
|
|
||||||
|
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||||
|
print("haptics")
|
||||||
|
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// JoystickView.swift
|
||||||
|
// Pomelo
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 30/9/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftUIJoystick
|
||||||
|
|
||||||
|
public struct Joystick: View {
|
||||||
|
@State var iscool: Bool? = nil
|
||||||
|
|
||||||
|
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||||
|
var dragDiameter: CGFloat {
|
||||||
|
var selfs = CGFloat(160)
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
return selfs * 1.2
|
||||||
|
}
|
||||||
|
return selfs
|
||||||
|
}
|
||||||
|
private let shape: JoystickShape = .circle
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack{
|
||||||
|
JoystickBuilder(
|
||||||
|
monitor: self.joystickMonitor,
|
||||||
|
width: self.dragDiameter,
|
||||||
|
shape: .circle,
|
||||||
|
background: {
|
||||||
|
Text("")
|
||||||
|
.hidden()
|
||||||
|
},
|
||||||
|
foreground: {
|
||||||
|
Circle().fill(Color.gray)
|
||||||
|
.opacity(0.7)
|
||||||
|
},
|
||||||
|
locksInPlace: false)
|
||||||
|
.onChange(of: self.joystickMonitor.xyPoint) { newValue in
|
||||||
|
let scaledX = Float(newValue.x)
|
||||||
|
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
||||||
|
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||||
|
|
||||||
|
if iscool != nil {
|
||||||
|
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
||||||
|
} else {
|
||||||
|
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
143
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift
Normal file
143
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/Air.swift
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// Credit https://github.com/heestand-xyz/AirKit
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public class Air {
|
||||||
|
|
||||||
|
static let shared = Air()
|
||||||
|
|
||||||
|
public var connected: Bool = false {
|
||||||
|
didSet {
|
||||||
|
connectionCallbacks.forEach({ $0(connected) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var connectionCallbacks: [(Bool) -> ()] = []
|
||||||
|
|
||||||
|
var airScreen: UIScreen?
|
||||||
|
var airWindow: UIWindow?
|
||||||
|
|
||||||
|
var hostingController: UIHostingController<AnyView>?
|
||||||
|
|
||||||
|
var appIsActive: Bool { UIApplication.shared.applicationState == .active }
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(didConnect),
|
||||||
|
name: UIScreen.didConnectNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(didDisconnect),
|
||||||
|
name: UIScreen.didDisconnectNotification, object: nil)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive),
|
||||||
|
name: UIApplication.didBecomeActiveNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive),
|
||||||
|
name: UIApplication.willResignActiveNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func check() {
|
||||||
|
if let connectedScreen = UIScreen.screens.first(where: { $0 != .main }) {
|
||||||
|
add(screen: connectedScreen) { success in
|
||||||
|
guard success else { return }
|
||||||
|
self.connected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func play(_ view: AnyView) {
|
||||||
|
Air.shared.hostingController = UIHostingController<AnyView>(rootView: view)
|
||||||
|
Air.shared.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func stop() {
|
||||||
|
Air.shared.remove()
|
||||||
|
Air.shared.hostingController = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func connection(_ callback: @escaping (Bool) -> ()) {
|
||||||
|
Air.shared.connectionCallbacks.append(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didConnect(sender: NSNotification) {
|
||||||
|
print("AirKit - Connect")
|
||||||
|
self.connected = true
|
||||||
|
guard let screen: UIScreen = sender.object as? UIScreen else { return }
|
||||||
|
add(screen: screen) { success in
|
||||||
|
guard success else { return }
|
||||||
|
self.connected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(screen: UIScreen, completion: @escaping (Bool) -> ()) {
|
||||||
|
|
||||||
|
print("AirKit - Add Screen")
|
||||||
|
|
||||||
|
airScreen = screen
|
||||||
|
|
||||||
|
airWindow = UIWindow(frame: airScreen!.bounds)
|
||||||
|
|
||||||
|
guard let viewController: UIViewController = hostingController else {
|
||||||
|
print("AirKit - Add - Failed: Hosting Controller Not Found")
|
||||||
|
completion(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
findWindowScene(for: airScreen!) { windowScene in
|
||||||
|
guard let airWindowScene: UIWindowScene = windowScene else {
|
||||||
|
print("AirKit - Add - Failed: Window Scene Not Found")
|
||||||
|
completion(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.airWindow?.rootViewController = viewController
|
||||||
|
self.airWindow?.windowScene = airWindowScene
|
||||||
|
self.airWindow?.isHidden = false
|
||||||
|
print("AirKit - Add Screen - Done")
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) {
|
||||||
|
print("AirKit - Find Window Scene")
|
||||||
|
var matchingWindowScene: UIWindowScene? = nil
|
||||||
|
let scenes = UIApplication.shared.connectedScenes
|
||||||
|
for scene in scenes {
|
||||||
|
if let windowScene = scene as? UIWindowScene {
|
||||||
|
if windowScene.screen == screen {
|
||||||
|
matchingWindowScene = windowScene
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let windowScene: UIWindowScene = matchingWindowScene else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.findWindowScene(for: screen, shouldRecurse: false) { windowScene in
|
||||||
|
completion(windowScene)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
completion(windowScene)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didDisconnect() {
|
||||||
|
print("AirKit - Disconnect")
|
||||||
|
remove()
|
||||||
|
connected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove() {
|
||||||
|
print("AirKit - Remove")
|
||||||
|
airWindow = nil
|
||||||
|
airScreen = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func didBecomeActive() {
|
||||||
|
print("AirKit - App Active")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func willResignActive() {
|
||||||
|
print("AirKit - App Inactive")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift
Normal file
13
src/MeloNX/MeloNX/App/Views/Emulation/AirPlay/AirPlay.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
|
||||||
|
func airPlay() -> some View {
|
||||||
|
print("AirKit - airPlay")
|
||||||
|
Air.play(AnyView(self))
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// EmulationView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 09/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// Emulation View
|
||||||
|
struct EmulationView: View {
|
||||||
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
|
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
||||||
|
@State var isAirplaying = Air.shared.connected
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
if isAirplaying {
|
||||||
|
Text("")
|
||||||
|
.onAppear {
|
||||||
|
Air.play(AnyView(MetalView(airplay: true).ignoresSafeArea()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MetalView(airplay: false) // The Emulation View
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Above Emulation View
|
||||||
|
|
||||||
|
if isVCA {
|
||||||
|
ControllerView() // Virtual Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssb {
|
||||||
|
Group {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
|
||||||
|
Button {
|
||||||
|
if let screenshot = Ryujinx.shared.emulationUIView.screenshot() {
|
||||||
|
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "square.and.arrow.up")
|
||||||
|
}
|
||||||
|
.frame(width: UIDevice.current.systemName.contains("iPadOS") ? 60 * 1.2 : 45, height: UIDevice.current.systemName.contains("iPadOS") ? 60 * 1.2 : 45)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
Air.shared.connectionCallbacks.append { cool in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
isAirplaying = cool
|
||||||
|
print(cool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// MetalView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 09/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MetalKit
|
||||||
|
|
||||||
|
struct MetalView: UIViewRepresentable {
|
||||||
|
|
||||||
|
var airplay: Bool // just in case :3
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
let metalLayer = Ryujinx.shared.metalLayer!
|
||||||
|
|
||||||
|
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 }) {
|
||||||
|
Ryujinx.shared.emulationUIView.layer.addSublayer(metalLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ryujinx.shared.emulationUIView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIView, context: Context) {
|
||||||
|
// nothin
|
||||||
|
}
|
||||||
|
}
|
112
src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift
Normal file
112
src/MeloNX/MeloNX/App/Views/GamesList/GameInfoSheet.swift
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
//
|
||||||
|
// GameInfoSheet.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Bella on 08/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct GameInfoSheet: View {
|
||||||
|
let game: Game
|
||||||
|
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
iOSNav {
|
||||||
|
VStack {
|
||||||
|
if let icon = game.icon {
|
||||||
|
Image(uiImage: icon)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 250, height: 250)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.padding()
|
||||||
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
UIImageWriteToSavedPhotosAlbum(icon, nil, nil, nil)
|
||||||
|
} label: {
|
||||||
|
Label("Save to Photos", systemImage: "square.and.arrow.down")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Image(systemName: "questionmark.circle")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 150, height: 150)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("**\(game.titleName)** | \(game.titleId.capitalized)")
|
||||||
|
Text(game.developer)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 3)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Information")
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
|
||||||
|
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("**File Type:** .\(getFileType(game.fileURL))")
|
||||||
|
Text("**Game URL:** \(trimGameURL(game.fileURL))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 5)
|
||||||
|
.navigationTitle(game.titleName)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("Done") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchFileSize(for gamePath: URL) -> UInt64? {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
do {
|
||||||
|
let attributes = try fileManager.attributesOfItem(atPath: gamePath.path)
|
||||||
|
if let size = attributes[FileAttributeKey.size] as? UInt64 {
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error getting file size: \(error)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimGameURL(_ url: URL) -> String {
|
||||||
|
let path = url.path
|
||||||
|
if let range = path.range(of: "/roms/") {
|
||||||
|
return String(path[range.lowerBound...])
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileType(_ url: URL) -> String {
|
||||||
|
let path = url.path
|
||||||
|
if let range = path.range(of: ".") {
|
||||||
|
return String(path[range.upperBound...])
|
||||||
|
}
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
524
src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift
Normal file
524
src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift
Normal file
@ -0,0 +1,524 @@
|
|||||||
|
//
|
||||||
|
// GameListView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
extension UTType {
|
||||||
|
static let nsp = UTType(exportedAs: "com.nintendo.switch-package")
|
||||||
|
static let xci = UTType(exportedAs: "com.nintendo.switch-cartridge")
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GameLibraryView: View {
|
||||||
|
@Binding var startemu: Game?
|
||||||
|
// @State var importDLCs = false
|
||||||
|
@State private var searchText = ""
|
||||||
|
@State private var isSearching = false
|
||||||
|
@AppStorage("recentGames") private var recentGamesData: Data = Data()
|
||||||
|
@State private var recentGames: [Game] = []
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@State var firmwareInstaller = false
|
||||||
|
@State var firmwareversion = "0"
|
||||||
|
@State var isImporting: Bool = false
|
||||||
|
@State var startgame = false
|
||||||
|
@State var isSelectingGameFile = false
|
||||||
|
@State var isViewingGameInfo: Bool = false
|
||||||
|
@State var gameInfo: Game?
|
||||||
|
var games: Binding<[Game]> {
|
||||||
|
Binding(
|
||||||
|
get: { Ryujinx.shared.games },
|
||||||
|
set: { Ryujinx.shared.games = $0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var filteredGames: [Game] {
|
||||||
|
if searchText.isEmpty {
|
||||||
|
return Ryujinx.shared.games
|
||||||
|
}
|
||||||
|
return Ryujinx.shared.games.filter {
|
||||||
|
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
||||||
|
$0.developer.localizedCaseInsensitiveContains(searchText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
iOSNav {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(alignment: .leading, spacing: 20) {
|
||||||
|
if !isSearching {
|
||||||
|
Text("Games")
|
||||||
|
.font(.system(size: 34, weight: .bold))
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.top, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Ryujinx.shared.games.isEmpty {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Image(systemName: "gamecontroller.fill")
|
||||||
|
.font(.system(size: 64))
|
||||||
|
.foregroundColor(.secondary.opacity(0.7))
|
||||||
|
.padding(.top, 60)
|
||||||
|
Text("No Games Found")
|
||||||
|
.font(.title2.bold())
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("Add ROM, Keys and Firmware to get started")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.top, 40)
|
||||||
|
} else {
|
||||||
|
if !isSearching && !recentGames.isEmpty {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
Text("Recent")
|
||||||
|
.font(.title2.bold())
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
LazyHStack(spacing: 16) {
|
||||||
|
ForEach(recentGames) { game in
|
||||||
|
RecentGameCard(game: game, startemu: $startemu)
|
||||||
|
.onTapGesture {
|
||||||
|
addToRecentGames(game)
|
||||||
|
startemu = game
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
Text("All Games")
|
||||||
|
.font(.title2.bold())
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
LazyVStack(spacing: 2) {
|
||||||
|
ForEach(filteredGames) { game in
|
||||||
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
|
.onTapGesture {
|
||||||
|
addToRecentGames(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyVStack(spacing: 2) {
|
||||||
|
ForEach(filteredGames) { game in
|
||||||
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
|
.onTapGesture {
|
||||||
|
addToRecentGames(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
loadRecentGames()
|
||||||
|
|
||||||
|
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
||||||
|
firmwareversion = (firmware == "" ? "0" : firmware)
|
||||||
|
}
|
||||||
|
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let url):
|
||||||
|
do {
|
||||||
|
let fun = url.startAccessingSecurityScopedResource()
|
||||||
|
let path = url.path
|
||||||
|
|
||||||
|
Ryujinx.shared.installFirmware(firmwarePath: path)
|
||||||
|
|
||||||
|
firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion())
|
||||||
|
if fun {
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Button {
|
||||||
|
isSelectingGameFile.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Menu {
|
||||||
|
Text("Firmware Version: \(firmwareversion)")
|
||||||
|
.tint(.white)
|
||||||
|
|
||||||
|
if firmwareversion == "0" {
|
||||||
|
Button {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
firmwareInstaller.toggle()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Install Firmware")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Menu("Firmware") {
|
||||||
|
Button {
|
||||||
|
Ryujinx.shared.removeFirmware()
|
||||||
|
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
||||||
|
firmwareversion = (firmware == "" ? "0" : firmware)
|
||||||
|
} label: {
|
||||||
|
Text("Remove Firmware")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
let game = Game(containerFolder: URL(string: "none")!, fileType: .item, fileURL: URL(string: "MiiMaker")!, titleName: "Mii Maker", titleId: "0", developer: "Nintendo", version: firmwareversion)
|
||||||
|
|
||||||
|
self.startemu = game
|
||||||
|
} label: {
|
||||||
|
Text("Mii Maker")
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
isImporting.toggle()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Open game from system")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
var sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
||||||
|
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
|
sharedurl = documentsUrl.absoluteString
|
||||||
|
}
|
||||||
|
print(sharedurl)
|
||||||
|
let furl = URL(string: sharedurl)!
|
||||||
|
if UIApplication.shared.canOpenURL(furl) {
|
||||||
|
UIApplication.shared.open(furl, options: [:])
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("Show MeloNX Folder")
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "ellipsis.circle")
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
.searchable(text: $searchText)
|
||||||
|
.onChange(of: searchText) { _ in
|
||||||
|
isSearching = !searchText.isEmpty
|
||||||
|
}
|
||||||
|
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .folder]) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let url):
|
||||||
|
guard url.startAccessingSecurityScopedResource() else {
|
||||||
|
print("Failed to access security-scoped resource")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer { url.stopAccessingSecurityScopedResource() }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let handle = try FileHandle(forReadingFrom: url)
|
||||||
|
let fileExtension = (url.pathExtension as NSString).utf8String
|
||||||
|
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
|
||||||
|
|
||||||
|
var gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
|
||||||
|
|
||||||
|
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: url)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
startemu = game
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let err):
|
||||||
|
print("File import failed: \(err.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fileImporter(isPresented: $isSelectingGameFile, allowedContentTypes: [.nsp, .xci, .zip, .folder]) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let url):
|
||||||
|
guard url.startAccessingSecurityScopedResource() else {
|
||||||
|
print("Failed to access security-scoped resource")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer { url.stopAccessingSecurityScopedResource() }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
||||||
|
|
||||||
|
if !fileManager.fileExists(atPath: romsDirectory.path) {
|
||||||
|
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
let destinationURL = romsDirectory.appendingPathComponent(url.lastPathComponent)
|
||||||
|
try fileManager.copyItem(at: url, to: destinationURL)
|
||||||
|
|
||||||
|
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||||
|
} catch {
|
||||||
|
print("Error copying game file: \(error)")
|
||||||
|
}
|
||||||
|
case .failure(let err):
|
||||||
|
print("File import failed: \(err.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: Binding(
|
||||||
|
get: { isViewingGameInfo && gameInfo != nil },
|
||||||
|
set: { newValue in
|
||||||
|
if !newValue {
|
||||||
|
isViewingGameInfo = false
|
||||||
|
gameInfo = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
if let game = gameInfo {
|
||||||
|
GameInfoSheet(game: game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func addToRecentGames(_ game: Game) {
|
||||||
|
recentGames.removeAll { $0.id == game.id }
|
||||||
|
|
||||||
|
recentGames.insert(game, at: 0)
|
||||||
|
|
||||||
|
if recentGames.count > 5 {
|
||||||
|
recentGames = Array(recentGames.prefix(5))
|
||||||
|
}
|
||||||
|
|
||||||
|
saveRecentGames()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveRecentGames() {
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let data = try encoder.encode(recentGames)
|
||||||
|
recentGamesData = data
|
||||||
|
} catch {
|
||||||
|
print("Error saving recent games: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadRecentGames() {
|
||||||
|
do {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
recentGames = try decoder.decode([Game].self, from: recentGamesData)
|
||||||
|
} catch {
|
||||||
|
print("Error loading recent games: \(error)")
|
||||||
|
recentGames = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Delete Game Function
|
||||||
|
func deleteGame(game: Game) {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
do {
|
||||||
|
try fileManager.removeItem(at: game.fileURL)
|
||||||
|
Ryujinx.shared.games.removeAll { $0.id == game.id }
|
||||||
|
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||||
|
} catch {
|
||||||
|
print("Error deleting game: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -Game Model
|
||||||
|
extension Game: Codable {
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case titleName, titleId, developer, version, fileURL
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
titleName = try container.decode(String.self, forKey: .titleName)
|
||||||
|
titleId = try container.decode(String.self, forKey: .titleId)
|
||||||
|
developer = try container.decode(String.self, forKey: .developer)
|
||||||
|
version = try container.decode(String.self, forKey: .version)
|
||||||
|
fileURL = try container.decode(URL.self, forKey: .fileURL)
|
||||||
|
|
||||||
|
// Initialize other properties
|
||||||
|
self.containerFolder = fileURL.deletingLastPathComponent()
|
||||||
|
self.fileType = .item
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(titleName, forKey: .titleName)
|
||||||
|
try container.encode(titleId, forKey: .titleId)
|
||||||
|
try container.encode(developer, forKey: .developer)
|
||||||
|
try container.encode(version, forKey: .version)
|
||||||
|
try container.encode(fileURL, forKey: .fileURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -Recent Game Card
|
||||||
|
struct RecentGameCard: View {
|
||||||
|
let game: Game
|
||||||
|
@Binding var startemu: Game?
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: {
|
||||||
|
startemu = game
|
||||||
|
}) {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
if let icon = game.icon {
|
||||||
|
Image(uiImage: icon)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 140, height: 140)
|
||||||
|
.cornerRadius(12)
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.fill(colorScheme == .dark ?
|
||||||
|
Color(.systemGray5) : Color(.systemGray6))
|
||||||
|
.frame(width: 140, height: 140)
|
||||||
|
|
||||||
|
Image(systemName: "gamecontroller.fill")
|
||||||
|
.font(.system(size: 40))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(game.titleName)
|
||||||
|
.font(.subheadline.bold())
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
Text(game.developer)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -Game List Item
|
||||||
|
struct GameListRow: View {
|
||||||
|
let game: Game
|
||||||
|
@Binding var startemu: Game?
|
||||||
|
@Binding var games: [Game] // Add this binding
|
||||||
|
@Binding var isViewingGameInfo: Bool
|
||||||
|
@Binding var gameInfo: Game?
|
||||||
|
@State var gametoDelete: Game?
|
||||||
|
@State var showGameDeleteConfirmation: Bool = false
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: {
|
||||||
|
startemu = game
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
// Game Icon
|
||||||
|
if let icon = game.icon {
|
||||||
|
Image(uiImage: icon)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 45, height: 45)
|
||||||
|
.cornerRadius(8)
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.fill(colorScheme == .dark ?
|
||||||
|
Color(.systemGray5) : Color(.systemGray6))
|
||||||
|
.frame(width: 45, height: 45)
|
||||||
|
|
||||||
|
Image(systemName: "gamecontroller.fill")
|
||||||
|
.font(.system(size: 20))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game Info
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(game.titleName)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Text(game.developer)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "play.circle.fill")
|
||||||
|
.font(.title2)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.opacity(0.8)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
.contextMenu {
|
||||||
|
Section {
|
||||||
|
Button {
|
||||||
|
startemu = game
|
||||||
|
} label: {
|
||||||
|
Label("Play Now", systemImage: "play.fill")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
gameInfo = game
|
||||||
|
isViewingGameInfo.toggle()
|
||||||
|
} label: {
|
||||||
|
Label("Game Info", systemImage: "info.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
gametoDelete = game
|
||||||
|
showGameDeleteConfirmation.toggle()
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) {
|
||||||
|
Button("Delete", role: .destructive) {
|
||||||
|
if let game = gametoDelete {
|
||||||
|
deleteGame(game: game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
} message: {
|
||||||
|
Text("Are you sure you want to delete \(gametoDelete?.titleName ?? "this game")?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteGame(game: Game) {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
do {
|
||||||
|
try fileManager.removeItem(at: game.fileURL)
|
||||||
|
games.removeAll { $0.id == game.id }
|
||||||
|
} catch {
|
||||||
|
print("Error deleting game: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
100
src/MeloNX/MeloNX/App/Views/Logging/Logs.swift
Normal file
100
src/MeloNX/MeloNX/App/Views/Logging/Logs.swift
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// LogEntry.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 09/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LogEntry: Identifiable, Equatable {
|
||||||
|
let id = UUID()
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
static func == (lhs: LogEntry, rhs: LogEntry) -> Bool {
|
||||||
|
return lhs.id == rhs.id && lhs.text == rhs.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LogViewer: View {
|
||||||
|
@State private var logs: [LogEntry] = []
|
||||||
|
@State private var latestLogFilePath: String?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
VStack {
|
||||||
|
ForEach(logs) { log in
|
||||||
|
Text(log.text)
|
||||||
|
.padding(4)
|
||||||
|
.background(Color.black.opacity(0.7))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.transition(.move(edge: .top).combined(with: .opacity))
|
||||||
|
.animation(.easeOut(duration: 2), value: logs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
.onAppear {
|
||||||
|
findNewestLogFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNewestLogFile() {
|
||||||
|
let logsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("logs")
|
||||||
|
|
||||||
|
guard let directory = logsDirectory else { return }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let logFiles = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey], options: .skipsHiddenFiles)
|
||||||
|
|
||||||
|
// Sort files by modification date (newest first)
|
||||||
|
let sortedFiles = logFiles.sorted {
|
||||||
|
(try? $0.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date.distantPast >
|
||||||
|
(try? $1.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date.distantPast
|
||||||
|
}
|
||||||
|
|
||||||
|
if let newestLogFile = sortedFiles.first {
|
||||||
|
latestLogFilePath = newestLogFile.path
|
||||||
|
startReadingLogFile()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error reading log files: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startReadingLogFile() {
|
||||||
|
guard let path = latestLogFilePath else { return }
|
||||||
|
let fileHandle = try? FileHandle(forReadingAtPath: path)
|
||||||
|
fileHandle?.seekToEndOfFile()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(forName: .NSFileHandleDataAvailable, object: fileHandle, queue: .main) { _ in
|
||||||
|
if let data = fileHandle?.availableData, !data.isEmpty {
|
||||||
|
if let logLine = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
withAnimation {
|
||||||
|
logs.append(LogEntry(text: logLine))
|
||||||
|
}
|
||||||
|
// Remove old logs after a delay
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
withAnimation {
|
||||||
|
removelogfirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileHandle?.waitForDataInBackgroundAndNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
fileHandle?.waitForDataInBackgroundAndNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removelogfirst() {
|
||||||
|
logs.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
640
src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift
Normal file
640
src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
//
|
||||||
|
// SettingsView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 25/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftSVG
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@Binding var config: Ryujinx.Configuration
|
||||||
|
@Binding var MoltenVKSettings: [MoltenVKSettings]
|
||||||
|
|
||||||
|
@Binding var controllersList: [Controller]
|
||||||
|
@Binding var currentControllers: [Controller]
|
||||||
|
|
||||||
|
@Binding var onscreencontroller: Controller
|
||||||
|
@AppStorage("useTrollStore") var useTrollStore: Bool = false
|
||||||
|
|
||||||
|
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
|
||||||
|
|
||||||
|
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
|
||||||
|
|
||||||
|
var memoryManagerModes = [
|
||||||
|
("HostMapped", "Host (fast)"),
|
||||||
|
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
|
||||||
|
("SoftwarePageTable", "Software (slow)"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
|
||||||
|
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
|
||||||
|
|
||||||
|
@AppStorage("showScreenShotButton") var ssb: 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("oldWindowCode") var windowCode: Bool = false
|
||||||
|
|
||||||
|
|
||||||
|
@State private var showResolutionInfo = false
|
||||||
|
@State private var showAnisotropicInfo = false
|
||||||
|
@State private var searchText = ""
|
||||||
|
|
||||||
|
var filteredMemoryModes: [(String, String)] {
|
||||||
|
guard !searchText.isEmpty else { return memoryManagerModes }
|
||||||
|
return memoryManagerModes.filter { $0.1.localizedCaseInsensitiveContains(searchText) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
iOSNav {
|
||||||
|
List {
|
||||||
|
|
||||||
|
|
||||||
|
// Graphics & Performance
|
||||||
|
Section {
|
||||||
|
Picker(selection: $config.aspectRatio) {
|
||||||
|
ForEach(AspectRatio.allCases, id: \.self) { ratio in
|
||||||
|
Text(ratio.displayName).tag(ratio)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
labelWithIcon("Aspect Ratio", iconName: "rectangle.expand.vertical")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disableShaderCache) {
|
||||||
|
labelWithIcon("Shader Cache", iconName: "memorychip")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disablevsync) {
|
||||||
|
labelWithIcon("Disable VSync", iconName: "arrow.triangle.2.circlepath")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
|
||||||
|
Toggle(isOn: $config.enableTextureRecompression) {
|
||||||
|
labelWithIcon("Texture Recompression", iconName: "rectangle.compress.vertical")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disableDockedMode) {
|
||||||
|
labelWithIcon("Docked Mode", iconName: "dock.rectangle")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.macroHLE) {
|
||||||
|
labelWithIcon("Macro HLE", iconName: "gearshape")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
labelWithIcon("Resolution Scale", iconName: "magnifyingglass")
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
showResolutionInfo.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.help("Learn more about Resolution Scale")
|
||||||
|
.alert(isPresented: $showResolutionInfo) {
|
||||||
|
Alert(
|
||||||
|
title: Text("Resolution Scale"),
|
||||||
|
message: Text("Adjust the internal rendering resolution. Higher values improve visuals but may reduce performance."),
|
||||||
|
dismissButton: .default(Text("OK"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: $config.resscale, in: 0.1...3.0, step: 0.05) {
|
||||||
|
Text("Resolution Scale")
|
||||||
|
} minimumValueLabel: {
|
||||||
|
Text("0.1x")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} maximumValueLabel: {
|
||||||
|
Text("3.0x")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
Text("\(config.resscale, specifier: "%.2f")x")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
HStack {
|
||||||
|
labelWithIcon("Max Anisotropic Scale", iconName: "magnifyingglass")
|
||||||
|
.font(.headline)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
showAnisotropicInfo.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.help("Learn more about Max Anisotropic Scale")
|
||||||
|
.alert(isPresented: $showAnisotropicInfo) {
|
||||||
|
Alert(
|
||||||
|
title: Text("Max Anisotripic Scale"),
|
||||||
|
message: Text("Adjust the internal Anisotropic resolution. Higher values improve visuals but may reduce performance. Default at 0 lets game decide."),
|
||||||
|
dismissButton: .default(Text("OK"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider(value: $config.maxAnisotropy, in: 0...16.0, step: 0.1) {
|
||||||
|
Text("Resolution Scale")
|
||||||
|
} minimumValueLabel: {
|
||||||
|
Text("0x")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} maximumValueLabel: {
|
||||||
|
Text("16.0x")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
Text("\(config.maxAnisotropy, specifier: "%.2f")x")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
|
Toggle(isOn: $performacehud) {
|
||||||
|
labelWithIcon("Performance Overlay", iconName: "speedometer")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
} header: {
|
||||||
|
Text("Graphics & Performance")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Fine-tune graphics and performance to suit your device and preferences.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input Selector
|
||||||
|
Section {
|
||||||
|
if !controllersList.filter({ !currentControllers.contains($0) }).isEmpty {
|
||||||
|
DisclosureGroup("Unselected Controllers") {
|
||||||
|
ForEach(controllersList.filter { !currentControllers.contains($0) }) { controller in
|
||||||
|
var customBinding: Binding<Bool> {
|
||||||
|
Binding(
|
||||||
|
get: { currentControllers.contains(controller) },
|
||||||
|
set: { bool in
|
||||||
|
if !bool {
|
||||||
|
currentControllers.removeAll(where: { $0.id == controller.id })
|
||||||
|
} else {
|
||||||
|
currentControllers.append(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: customBinding) {
|
||||||
|
Text(controller.name)
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ForEach(currentControllers) { controller in
|
||||||
|
|
||||||
|
var customBinding: Binding<Bool> {
|
||||||
|
Binding(
|
||||||
|
get: { currentControllers.contains(controller) },
|
||||||
|
set: { bool in
|
||||||
|
if !bool {
|
||||||
|
currentControllers.removeAll(where: { $0.id == controller.id })
|
||||||
|
} else {
|
||||||
|
currentControllers.append(controller)
|
||||||
|
}
|
||||||
|
// toggleController(controller)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if customBinding.wrappedValue {
|
||||||
|
DisclosureGroup {
|
||||||
|
Toggle(isOn: customBinding) {
|
||||||
|
Text(controller.name)
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.onDrag({ NSItemProvider() })
|
||||||
|
} label: {
|
||||||
|
|
||||||
|
if let controller = currentControllers.firstIndex(where: { $0.id == controller.id } ) {
|
||||||
|
Text("Player \(controller + 1)")
|
||||||
|
.onAppear() {
|
||||||
|
// print(currentControllers.firstIndex(where: { $0.id == controller.id }) ?? 0)
|
||||||
|
print(currentControllers.count)
|
||||||
|
|
||||||
|
if currentControllers.count > 2 {
|
||||||
|
print(currentControllers[1])
|
||||||
|
print(currentControllers[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onMove { from, to in
|
||||||
|
currentControllers.move(fromOffsets: from, toOffset: to)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Input Selector")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Select input devices and on-screen controls to play with. ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input Settings
|
||||||
|
Section {
|
||||||
|
|
||||||
|
Toggle(isOn: $config.listinputids) {
|
||||||
|
labelWithIcon("List Input IDs", iconName: "list.bullet")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $ryuDemo) {
|
||||||
|
labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.disabled(true)
|
||||||
|
} header: {
|
||||||
|
Text("Input Settings")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Configure input devices and on-screen controls for easier navigation and play.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU Mode
|
||||||
|
Section {
|
||||||
|
if filteredMemoryModes.isEmpty {
|
||||||
|
Text("No matches for \"\(searchText)\"")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} else {
|
||||||
|
Picker(selection: $config.memoryManagerMode) {
|
||||||
|
ForEach(filteredMemoryModes, id: \.0) { key, displayName in
|
||||||
|
Text(displayName).tag(key)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
labelWithIcon("Memory Manager Mode", iconName: "gearshape")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $config.disablePTC) {
|
||||||
|
labelWithIcon("Disable PTC", iconName: "cpu")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
|
if let cpuInfo = getCPUInfo(), cpuInfo.hasPrefix("Apple M") {
|
||||||
|
if #available (iOS 16.4, *) {
|
||||||
|
Toggle(isOn: .constant(false)) {
|
||||||
|
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.disabled(true)
|
||||||
|
.onAppear() {
|
||||||
|
print("CPU Info: \(cpuInfo)")
|
||||||
|
}
|
||||||
|
} else if getEntitlementValue("com.apple.private.hypervisor") {
|
||||||
|
Toggle(isOn: $config.hypervisor) {
|
||||||
|
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.onAppear() {
|
||||||
|
print("CPU Info: \(cpuInfo)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("CPU")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
Text("Select how memory is managed. 'Host (fast)' is best for most users.")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Section {
|
||||||
|
|
||||||
|
|
||||||
|
Toggle(isOn: $config.expandRam) {
|
||||||
|
labelWithIcon("Expand Guest Ram (6GB)", iconName: "exclamationmark.bubble")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.ignoreMissingServices) {
|
||||||
|
labelWithIcon("Ignore Missing Services", iconName: "waveform.path")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
} header: {
|
||||||
|
Text("Hacks")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other Settings
|
||||||
|
Section {
|
||||||
|
|
||||||
|
Toggle(isOn: $ssb) {
|
||||||
|
labelWithIcon("Screenshot Button", iconName: "square.and.arrow.up")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
|
if #available(iOS 17.0.1, *) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $syncqsubmits) {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
} header: {
|
||||||
|
Text("Miscellaneous Options")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
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
|
||||||
|
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 {
|
||||||
|
|
||||||
|
Toggle(isOn: $mVKPreFillBuffer) {
|
||||||
|
labelWithIcon("MVK: Pre-Fill Metal Command Buffers", iconName: "gearshape")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $config.dfsIntegrityChecks) {
|
||||||
|
labelWithIcon("Disable FS Integrity Checks", iconName: "checkmark.shield")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
labelWithIcon("Page Size", iconName: "textformat.size")
|
||||||
|
Spacer()
|
||||||
|
Text("\(String(Int(getpagesize())))")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField("Additional Arguments", text: Binding(
|
||||||
|
get: {
|
||||||
|
config.additionalArgs.joined(separator: " ")
|
||||||
|
},
|
||||||
|
set: { newValue in
|
||||||
|
config.additionalArgs = newValue
|
||||||
|
.split(separator: ",")
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespaces) }
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.textInputAutocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Ryujinx.shared.removeFirmware()
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Text("Remove Firmware")
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} label: {
|
||||||
|
Text("Advanced Options")
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Advanced")
|
||||||
|
.font(.title3.weight(.semibold))
|
||||||
|
.textCase(nil)
|
||||||
|
.headerProminence(.increased)
|
||||||
|
} footer: {
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing).")
|
||||||
|
} 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\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
|
||||||
|
.navigationTitle("Settings")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.listStyle(.insetGrouped)
|
||||||
|
.onAppear {
|
||||||
|
if let configs = loadSettings() {
|
||||||
|
self.config = configs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: config) { _ in
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationViewStyle(.stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func toggleController(_ controller: Controller) {
|
||||||
|
if currentControllers.contains(where: { $0.id == controller.id }) {
|
||||||
|
currentControllers.removeAll(where: { $0.id == controller.id })
|
||||||
|
} else {
|
||||||
|
currentControllers.append(controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveSettings() {
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
|
||||||
|
print("Saving Settings")
|
||||||
|
#else
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = .prettyPrinted
|
||||||
|
let data = try encoder.encode(config)
|
||||||
|
let jsonString = String(data: data, encoding: .utf8)
|
||||||
|
UserDefaults.standard.set(jsonString, forKey: "config")
|
||||||
|
} catch {
|
||||||
|
print("Failed to save settings: \(error)")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCPUInfo() -> String? {
|
||||||
|
let device = MTLCreateSystemDefaultDevice()
|
||||||
|
|
||||||
|
let gpu = device?.name
|
||||||
|
print("GPU: " + (gpu ?? ""))
|
||||||
|
return gpu
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Original loadSettings function assumed to exist
|
||||||
|
func loadSettings() -> Ryujinx.Configuration? {
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
print("Running on Simulator")
|
||||||
|
|
||||||
|
return Ryujinx.Configuration(gamepath: "")
|
||||||
|
#else
|
||||||
|
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
|
||||||
|
let data = jsonString.data(using: .utf8) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let configs = try decoder.decode(Ryujinx.Configuration.self, from: data)
|
||||||
|
return configs
|
||||||
|
} catch {
|
||||||
|
print("Failed to load settings: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
if iconName.hasSuffix(".svg"){
|
||||||
|
if let flipimage, flipimage {
|
||||||
|
SVGView(svgName: iconName, color: .blue)
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
|
||||||
|
} else {
|
||||||
|
SVGView(svgName: iconName, color: .blue)
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
}
|
||||||
|
} else if !iconName.isEmpty {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
.foregroundStyle(.blue)
|
||||||
|
}
|
||||||
|
Text(text)
|
||||||
|
}
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SVGView: UIViewRepresentable {
|
||||||
|
var svgName: String
|
||||||
|
var color: Color = Color.black
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
var svgName = svgName
|
||||||
|
var hammock = UIView()
|
||||||
|
|
||||||
|
if svgName.hasSuffix(".svg") {
|
||||||
|
svgName.removeLast(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let svgLayer = UIView(SVGNamed: svgName) { svgLayer in
|
||||||
|
svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color
|
||||||
|
svgLayer.resizeToFit(hammock.frame)
|
||||||
|
hammock.layer.addSublayer(svgLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hammock
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIView, context: Context) {
|
||||||
|
// Update the SVG view's fill color when the color changes
|
||||||
|
if let svgLayer = uiView.layer.sublayers?.first as? CAShapeLayer {
|
||||||
|
svgLayer.fillColor = UIColor(color).cgColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/MeloNX/MeloNX/App/Views/TabView/TabView.swift
Normal file
34
src/MeloNX/MeloNX/App/Views/TabView/TabView.swift
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// TabView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 10/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
|
||||||
|
struct MainTabView: View {
|
||||||
|
@Binding var startemu: Game?
|
||||||
|
@Binding var config: Ryujinx.Configuration
|
||||||
|
@Binding var MVKconfig: [MoltenVKSettings]
|
||||||
|
@Binding var controllersList: [Controller]
|
||||||
|
@Binding var currentControllers: [Controller]
|
||||||
|
|
||||||
|
@Binding var onscreencontroller: Controller
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
TabView {
|
||||||
|
GameLibraryView(startemu: $startemu)
|
||||||
|
.tabItem {
|
||||||
|
Label("Games", systemImage: "gamecontroller.fill")
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsView(config: $config, MoltenVKSettings: $MVKconfig, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
|
||||||
|
.tabItem {
|
||||||
|
Label("Settings", systemImage: "gear")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
|
"filename" : "nxgradientpng.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"data" : [
|
||||||
|
{
|
||||||
|
"filename" : "Troll-Face.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"universal-type-identifier" : "public.svg-image"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 26 KiB |
@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// Ryujinx-Header.h
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef RyujinxHeader
|
|
||||||
#define RyujinxHeader
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Declare the main_ryujinx_sdl function, matching the signature
|
|
||||||
int main_ryujinx_sdl(int argc, char **argv);
|
|
||||||
|
|
||||||
const char* get_game_controllers();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* RyujinxSDL_h */
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
|||||||
//
|
|
||||||
// VirtualController.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 28/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GameController
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
public var controllerCallback: (() -> Void)?
|
|
||||||
|
|
||||||
var VirtualController: GCVirtualController!
|
|
||||||
func showVirtualController() {
|
|
||||||
let config = GCVirtualController.Configuration()
|
|
||||||
if UserDefaults.standard.bool(forKey: "RyuDemoControls") {
|
|
||||||
config.elements = [
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
// GCInputRightThumbstick,
|
|
||||||
GCInputRightTrigger,
|
|
||||||
GCInputLeftTrigger,
|
|
||||||
GCInputLeftShoulder,
|
|
||||||
GCInputRightShoulder
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
config.elements = [
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
GCInputRightThumbstick,
|
|
||||||
GCInputRightTrigger,
|
|
||||||
GCInputLeftTrigger,
|
|
||||||
GCInputLeftShoulder,
|
|
||||||
GCInputRightShoulder
|
|
||||||
]
|
|
||||||
}
|
|
||||||
VirtualController = GCVirtualController(configuration: config)
|
|
||||||
VirtualController.connect { err in
|
|
||||||
print("controller connect: \(String(describing: err))")
|
|
||||||
patchMakeKeyAndVisible()
|
|
||||||
if let controllerCallback {
|
|
||||||
controllerCallback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitforcontroller() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
|
||||||
|
|
||||||
if let window = UIApplication.shared.windows.first {
|
|
||||||
// Function to recursively search for GCControllerView
|
|
||||||
func findGCControllerView(in view: UIView) -> UIView? {
|
|
||||||
// Check if current view is GCControllerView
|
|
||||||
if String(describing: type(of: view)) == "GCControllerView" {
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search through subviews
|
|
||||||
for subview in view.subviews {
|
|
||||||
if let found = findGCControllerView(in: subview) {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let gcControllerView = findGCControllerView(in: window) {
|
|
||||||
// Found the GCControllerView
|
|
||||||
print("Found GCControllerView:", gcControllerView)
|
|
||||||
|
|
||||||
if let theWindow = theWindow, (findGCControllerView(in: theWindow) == nil) {
|
|
||||||
theWindow.addSubview(gcControllerView)
|
|
||||||
|
|
||||||
theWindow.bringSubviewToFront(gcControllerView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 15.0, *)
|
|
||||||
func reconnectVirtualController() {
|
|
||||||
VirtualController.disconnect()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
VirtualController.connect { err in
|
|
||||||
print("reconnected: err \(String(describing: err))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
|||||||
//
|
|
||||||
// Untitled.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 28/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GameController
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var theWindow: UIWindow? = nil
|
|
||||||
extension UIWindow {
|
|
||||||
@objc func wdb_makeKeyAndVisible() {
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
|
||||||
}
|
|
||||||
self.wdb_makeKeyAndVisible()
|
|
||||||
theWindow = self
|
|
||||||
if #available(iOS 15.0, *) {
|
|
||||||
reconnectVirtualController()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if let window = theWindow {
|
|
||||||
waitforcontroller()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func patchMakeKeyAndVisible() {
|
|
||||||
let uiwindowClass = UIWindow.self
|
|
||||||
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
|
|
||||||
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
|
|
||||||
method_exchangeImplementations(m1, m2)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,244 +0,0 @@
|
|||||||
//
|
|
||||||
// Ryujinx.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
import SDL2
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
struct Controller: Identifiable, Hashable {
|
|
||||||
let id: String
|
|
||||||
let name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct iOSNav<Content: View>: View {
|
|
||||||
@ViewBuilder var content: () -> Content
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
NavigationStack(root: content)
|
|
||||||
} else {
|
|
||||||
NavigationView(content: content)
|
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
|
||||||
.navigationViewStyle(.stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Ryujinx {
|
|
||||||
private var isRunning = false
|
|
||||||
|
|
||||||
@Published var controllerMap: [Controller] = []
|
|
||||||
|
|
||||||
static let shared = Ryujinx()
|
|
||||||
|
|
||||||
private init() {}
|
|
||||||
|
|
||||||
public struct Configuration : Codable {
|
|
||||||
var gamepath: String
|
|
||||||
var inputids: [String]
|
|
||||||
var resscale: Float
|
|
||||||
var debuglogs: Bool
|
|
||||||
var tracelogs: Bool
|
|
||||||
var nintendoinput: Bool
|
|
||||||
var enableInternet: Bool
|
|
||||||
var listinputids: Bool
|
|
||||||
var fullscreen: Bool
|
|
||||||
var memoryManagerMode: String
|
|
||||||
var disableVSync: Bool
|
|
||||||
var disableShaderCache: Bool
|
|
||||||
var disableDockedMode: Bool
|
|
||||||
var enableTextureRecompression: Bool
|
|
||||||
var additionalArgs: [String]
|
|
||||||
|
|
||||||
|
|
||||||
init(gamepath: String,
|
|
||||||
inputids: [String] = [],
|
|
||||||
debuglogs: Bool = false,
|
|
||||||
tracelogs: Bool = false,
|
|
||||||
listinputids: Bool = false,
|
|
||||||
fullscreen: Bool = true,
|
|
||||||
memoryManagerMode: String = "HostMapped",
|
|
||||||
disableVSync: Bool = false,
|
|
||||||
disableShaderCache: Bool = false,
|
|
||||||
disableDockedMode: Bool = false,
|
|
||||||
nintendoinput: Bool = true,
|
|
||||||
enableInternet: Bool = false,
|
|
||||||
enableTextureRecompression: Bool = true,
|
|
||||||
additionalArgs: [String] = [],
|
|
||||||
resscale: Float = 1.00
|
|
||||||
) {
|
|
||||||
self.gamepath = gamepath
|
|
||||||
self.inputids = inputids
|
|
||||||
self.debuglogs = debuglogs
|
|
||||||
self.tracelogs = tracelogs
|
|
||||||
self.listinputids = listinputids
|
|
||||||
self.fullscreen = fullscreen
|
|
||||||
self.disableVSync = disableVSync
|
|
||||||
self.disableShaderCache = disableShaderCache
|
|
||||||
self.disableDockedMode = disableDockedMode
|
|
||||||
self.enableTextureRecompression = enableTextureRecompression
|
|
||||||
self.additionalArgs = additionalArgs
|
|
||||||
self.memoryManagerMode = memoryManagerMode
|
|
||||||
self.resscale = resscale
|
|
||||||
self.nintendoinput = nintendoinput
|
|
||||||
self.enableInternet = enableInternet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func start(with config: Configuration) throws {
|
|
||||||
guard !isRunning else {
|
|
||||||
throw RyujinxError.alreadyRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning = true
|
|
||||||
|
|
||||||
// Start The Emulation on the main thread
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
do {
|
|
||||||
let args = self.buildCommandLineArgs(from: config)
|
|
||||||
|
|
||||||
// Convert Arguments to ones that Ryujinx can Read
|
|
||||||
let cArgs = args.map { strdup($0) }
|
|
||||||
defer { cArgs.forEach { free($0) } }
|
|
||||||
var argvPtrs = cArgs
|
|
||||||
|
|
||||||
// Start the emulation
|
|
||||||
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
|
||||||
|
|
||||||
if result != 0 {
|
|
||||||
self.isRunning = false
|
|
||||||
throw RyujinxError.executionError(code: result)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
self.isRunning = false
|
|
||||||
Self.log("Emulation failed to start: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func stop() throws {
|
|
||||||
guard isRunning else {
|
|
||||||
throw RyujinxError.notRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var running: Bool {
|
|
||||||
return isRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
private func buildCommandLineArgs(from config: Configuration) -> [String] {
|
|
||||||
var args: [String] = []
|
|
||||||
|
|
||||||
// Add the game path
|
|
||||||
args.append(config.gamepath)
|
|
||||||
|
|
||||||
// Starts with vulkan
|
|
||||||
args.append("--graphics-backend")
|
|
||||||
args.append("Vulkan")
|
|
||||||
|
|
||||||
// Fixes the Stubs.DispatchLoop Crash
|
|
||||||
args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode])
|
|
||||||
if config.fullscreen {
|
|
||||||
args.append(contentsOf: ["--exclusive-fullscreen", String(config.fullscreen)])
|
|
||||||
args.append(contentsOf: ["--exclusive-fullscreen-width", "1280"])
|
|
||||||
args.append(contentsOf: ["--exclusive-fullscreen-height", "720"])
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.resscale != 1 {
|
|
||||||
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.nintendoinput {
|
|
||||||
args.append("--correct-ons-controller")
|
|
||||||
}
|
|
||||||
if config.enableInternet {
|
|
||||||
args.append("--enable-internet-connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding default args directly into additionalArgs
|
|
||||||
if config.disableVSync {
|
|
||||||
args.append("--disable-vsync")
|
|
||||||
}
|
|
||||||
if config.disableShaderCache {
|
|
||||||
args.append("--disable-shader-cache")
|
|
||||||
}
|
|
||||||
if config.disableDockedMode {
|
|
||||||
args.append("--disable-docked-mode")
|
|
||||||
}
|
|
||||||
if config.enableTextureRecompression {
|
|
||||||
args.append("--enable-texture-recompression")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.debuglogs {
|
|
||||||
args.append(contentsOf: ["--enable-debug-logs"])
|
|
||||||
}
|
|
||||||
if config.tracelogs {
|
|
||||||
args.append(contentsOf: ["--enable-trace-logs"])
|
|
||||||
}
|
|
||||||
|
|
||||||
// List the input ids
|
|
||||||
if config.listinputids {
|
|
||||||
args.append(contentsOf: ["--list-inputs-ids"])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the input ids (limit to 4 just in case)
|
|
||||||
if !config.inputids.isEmpty {
|
|
||||||
config.inputids.prefix(4).enumerated().forEach { index, inputId in
|
|
||||||
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apped any additional arguments
|
|
||||||
args.append(contentsOf: config.additionalArgs)
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConnectedControllers() -> [Controller] {
|
|
||||||
|
|
||||||
guard let jsonPtr = get_game_controllers() else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the unmanaged memory (C string) to a Swift String
|
|
||||||
let jsonString = String(cString: jsonPtr)
|
|
||||||
|
|
||||||
var controllers: [Controller] = []
|
|
||||||
|
|
||||||
// Splitting the string by newline
|
|
||||||
let lines = jsonString.components(separatedBy: "\n")
|
|
||||||
|
|
||||||
// Parsing each line
|
|
||||||
for line in lines {
|
|
||||||
if line.contains(":") {
|
|
||||||
let parts = line.components(separatedBy: ":")
|
|
||||||
if parts.count == 2 {
|
|
||||||
let id = parts[0].trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
let name = parts[1].trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
controllers.append(Controller(id: id, name: name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return controllers
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static func log(_ message: String) {
|
|
||||||
print("[Ryujinx] \(message)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Hypervisor.framework/Hypervisor
Executable file
BIN
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Hypervisor.framework/Hypervisor
Executable file
Binary file not shown.
Binary file not shown.
@ -0,0 +1,101 @@
|
|||||||
|
<?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>Info.plist</key>
|
||||||
|
<data>
|
||||||
|
kW04s165Fr3AhY1rHcISuPzpuPA=
|
||||||
|
</data>
|
||||||
|
</dict>
|
||||||
|
<key>files2</key>
|
||||||
|
<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>
|
@ -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>
|
Binary file not shown.
Binary file not shown.
@ -2,7 +2,73 @@
|
|||||||
<!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>
|
||||||
|
<string>83f67a0a96bd8628a150d7853e360db5bae64e7769524fae399c4b8e7e6aff17</string>
|
||||||
|
<key>NSUserActivityTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>LaunchGameIntent</string>
|
||||||
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.archive</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Nintendo Switch Package</string>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.nintendo.switch-package</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>nsp</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<string>application/x-nsp</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
<string>public.archive</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Nintendo Switch Cartridge</string>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>com.nintendo.switch-cartridge</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>xci</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<string>application/x-xci</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -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>
|
||||||
|
@ -6,12 +6,258 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct MeloNXApp: App {
|
struct MeloNXApp: App {
|
||||||
|
|
||||||
|
@State var showed = false
|
||||||
|
@Environment(\.scenePhase) var scenePhase
|
||||||
|
@State var alert: UIAlertController? = nil
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
|
ZStack {
|
||||||
|
if showed || DRM != 1 {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
} else {
|
||||||
|
Group {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Loading...")
|
||||||
|
ProgressView()
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(UIDevice.current.identifierForVendor?.uuidString ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
initR()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.background(Color.black.opacity(1))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initR() {
|
||||||
|
if DRM == 1 {
|
||||||
|
DispatchQueue.main.async { [self] in
|
||||||
|
// drmcheck()
|
||||||
|
InitializeRyujinx() { bool in
|
||||||
|
if bool {
|
||||||
|
print("Ryujinx Files Initialized Successfully")
|
||||||
|
DispatchQueue.main.async { [self] in
|
||||||
|
withAnimation {
|
||||||
|
showed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
|
InitializeRyujinx() { bool in
|
||||||
|
if !bool, (scenePhase != .background || scenePhase == .inactive) {
|
||||||
|
withAnimation {
|
||||||
|
showed = false
|
||||||
|
}
|
||||||
|
if !(alert?.isViewLoaded ?? false) {
|
||||||
|
alert = showDMCAAlert()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
alert?.dismiss(animated: true)
|
||||||
|
showed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
showDMCAAlert()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func showAlert() -> UIAlertController? {
|
||||||
|
// Create the alert controller
|
||||||
|
if let mainWindow = UIApplication.shared.windows.last {
|
||||||
|
let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert)
|
||||||
|
|
||||||
|
// Add a text field to the alert
|
||||||
|
alertController.addTextField { textField in
|
||||||
|
textField.placeholder = "Enter key here"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the "OK" action
|
||||||
|
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||||
|
// Get the text entered in the text field
|
||||||
|
if let textField = alertController.textFields?.first, let enteredText = textField.text {
|
||||||
|
print("Entered text: \(enteredText)")
|
||||||
|
UserDefaults.standard.set(enteredText, forKey: "MeloDRMID")
|
||||||
|
// drmcheck() { bool in
|
||||||
|
// if bool {
|
||||||
|
// showed = true
|
||||||
|
// } else {
|
||||||
|
// exit(0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alertController.addAction(okAction)
|
||||||
|
|
||||||
|
// Add a "Cancel" action
|
||||||
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
|
alertController.addAction(cancelAction)
|
||||||
|
|
||||||
|
// Present the alert
|
||||||
|
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
||||||
|
|
||||||
|
return alertController
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func showDMCAAlert() -> UIAlertController? {
|
||||||
|
if let mainWindow = UIApplication.shared.windows.first {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return alertController
|
||||||
|
} else {
|
||||||
|
// uhoh
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func drmcheck(completion: @escaping (Bool) -> Void) {
|
||||||
|
if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() {
|
||||||
|
if let value = UserDefaults.standard.string(forKey: "MeloDRMID") {
|
||||||
|
if let url = URL(string: "https://mx.stossy11.com/auth/\(value)/\(base64device)") {
|
||||||
|
print(url)
|
||||||
|
// Create a URLSession
|
||||||
|
let session = URLSession.shared
|
||||||
|
|
||||||
|
// Create a data task
|
||||||
|
let task = session.dataTask(with: url) { data, response, error in
|
||||||
|
// Handle errors
|
||||||
|
if let error = error {
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check response and data
|
||||||
|
if let response = response as? HTTPURLResponse, response.statusCode == 200 {
|
||||||
|
print("Successfully Recieved API Data")
|
||||||
|
completion(true)
|
||||||
|
} else if let response = response as? HTTPURLResponse, response.statusCode == 201 {
|
||||||
|
print("Successfully Created Auth UUID")
|
||||||
|
completion(true)
|
||||||
|
} else {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the task
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func InitializeRyujinx(completion: @escaping (Bool) -> Void) {
|
||||||
|
let path = "aHR0cHM6Ly9teC5zdG9zc3kxMS5jb20v"
|
||||||
|
|
||||||
|
guard let value = Bundle.main.object(forInfoDictionaryKey: "MeloID") as? String, !value.isEmpty else {
|
||||||
|
completion(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (detectRoms(path: path) != value) {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
let configuration = URLSessionConfiguration.default
|
||||||
|
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
||||||
|
configuration.urlCache = nil
|
||||||
|
|
||||||
|
let session = URLSession(configuration: configuration)
|
||||||
|
|
||||||
|
guard let url = URL(string: addFolders(path)!) else {
|
||||||
|
completion(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = session.dataTask(with: url) { data, response, error in
|
||||||
|
if error != nil {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
completion(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpResponse.statusCode == 200 {
|
||||||
|
completion(true)
|
||||||
|
} else {
|
||||||
|
completion(false)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectRoms(path string: String) -> String {
|
||||||
|
let inputData = Data(string.utf8)
|
||||||
|
let romHash = SHA256.hash(data: inputData)
|
||||||
|
return romHash.compactMap { String(format: "%02x", $0) }.joined()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func addFolders(_ folderPath: String) -> String? {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
if let data = Data(base64Encoded: folderPath),
|
||||||
|
let decodedString = String(data: data, encoding: .utf8), let fileURL = UIDevice.current.identifierForVendor?.uuidString {
|
||||||
|
return decodedString + "auth/" + fileURL + "/"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
func print() {
|
||||||
|
Swift.print(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,245 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SDL2
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
struct MoltenVKSettings: Codable, Hashable {
|
|
||||||
let string: String
|
|
||||||
var bool: Bool?
|
|
||||||
var value: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
// MARK: - Properties
|
|
||||||
@State private var theWindow: UIWindow?
|
|
||||||
@State private var virtualController: GCVirtualController?
|
|
||||||
@State private var game: URL?
|
|
||||||
@State private var controllersList: [Controller] = []
|
|
||||||
@State private var currentControllers: [Controller] = []
|
|
||||||
@State private var config: Ryujinx.Configuration
|
|
||||||
@State private var settings: [MoltenVKSettings]
|
|
||||||
@State private var isVirtualControllerActive: Bool = false
|
|
||||||
|
|
||||||
// MARK: - Initialization
|
|
||||||
init() {
|
|
||||||
let defaultConfig = Ryujinx.Configuration(gamepath: "")
|
|
||||||
_config = State(initialValue: defaultConfig)
|
|
||||||
|
|
||||||
let defaultSettings: [MoltenVKSettings] = [
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"),
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1")
|
|
||||||
]
|
|
||||||
_settings = State(initialValue: defaultSettings)
|
|
||||||
|
|
||||||
initializeSDL()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Body
|
|
||||||
var body: some View {
|
|
||||||
iOSNav {
|
|
||||||
if let game {
|
|
||||||
emulationView
|
|
||||||
} else {
|
|
||||||
mainMenuView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: isVirtualControllerActive) { newValue in
|
|
||||||
if newValue {
|
|
||||||
createVirtualController()
|
|
||||||
} else {
|
|
||||||
destroyVirtualController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - View Components
|
|
||||||
private var emulationView: some View {
|
|
||||||
ZStack {}
|
|
||||||
.onAppear {
|
|
||||||
setupEmulation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mainMenuView: some View {
|
|
||||||
HStack {
|
|
||||||
GameListView(startemu: $game)
|
|
||||||
.onAppear {
|
|
||||||
createVirtualController()
|
|
||||||
refreshControllersList()
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsListView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var settingsListView: some View {
|
|
||||||
List {
|
|
||||||
Section("Settings") {
|
|
||||||
NavigationLink("Config") {
|
|
||||||
SettingsView(config: $config, MoltenVKSettings: $settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section("Controller") {
|
|
||||||
Button("Refresh", action: refreshControllersList)
|
|
||||||
|
|
||||||
ForEach(controllersList, id: \.self) { controller in
|
|
||||||
if controller.name != "Apple Touch Controller" {
|
|
||||||
controllerRow(for: controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func controllerRow(for controller: Controller) -> some View {
|
|
||||||
HStack {
|
|
||||||
Button(controller.name) {
|
|
||||||
toggleController(controller)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
if currentControllers.contains(where: { $0.id == controller.id }) {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Controller Management
|
|
||||||
private func createVirtualController() {
|
|
||||||
let configuration = GCVirtualController.Configuration()
|
|
||||||
configuration.elements = [
|
|
||||||
/*
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputRightThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
*/
|
|
||||||
]
|
|
||||||
|
|
||||||
virtualController = GCVirtualController(configuration: configuration)
|
|
||||||
virtualController?.connect()
|
|
||||||
|
|
||||||
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
}
|
|
||||||
|
|
||||||
private func destroyVirtualController() {
|
|
||||||
virtualController?.disconnect()
|
|
||||||
virtualController = nil
|
|
||||||
|
|
||||||
// Remove virtual controller from current controllers
|
|
||||||
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
|
||||||
private func initializeSDL() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
setMoltenVKSettings()
|
|
||||||
SDL_SetMainReady()
|
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE)
|
|
||||||
SDL_Init(SDL_INIT_VIDEO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupEmulation() {
|
|
||||||
virtualController?.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
controllerCallback = {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
controllersList = Ryujinx.shared.getConnectedControllers()
|
|
||||||
currentControllers.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
if controllersList.count == 2,
|
|
||||||
controllersList.contains(where: { $0.name == "Apple Touch Controller" }) {
|
|
||||||
currentControllers.append(controllersList[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
print(currentControllers)
|
|
||||||
start(displayid: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
showVirtualController()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func refreshControllersList() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
|
||||||
controllersList = Ryujinx.shared.getConnectedControllers()
|
|
||||||
controllersList.removeAll(where: { $0.id == "0" })
|
|
||||||
|
|
||||||
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
|
|
||||||
if let controller = controllersList.first, !controllersList.isEmpty {
|
|
||||||
currentControllers.append(controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func toggleController(_ controller: Controller) {
|
|
||||||
if currentControllers.contains(where: { $0.id == controller.id }) {
|
|
||||||
currentControllers.removeAll(where: { $0.id == controller.id })
|
|
||||||
} else {
|
|
||||||
currentControllers.append(controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func start(displayid: UInt32) {
|
|
||||||
guard let game else { return }
|
|
||||||
|
|
||||||
config.gamepath = game.path
|
|
||||||
config.inputids = currentControllers.map(\.id)
|
|
||||||
|
|
||||||
allocateMemory()
|
|
||||||
|
|
||||||
do {
|
|
||||||
try Ryujinx.shared.start(with: config)
|
|
||||||
} catch {
|
|
||||||
print("Error: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func allocateMemory() {
|
|
||||||
let physicalMemory = ProcessInfo.processInfo.physicalMemory
|
|
||||||
let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024)
|
|
||||||
|
|
||||||
let pointer = UnsafeMutableRawPointer.allocate(
|
|
||||||
byteCount: Int(totalMemoryInGB),
|
|
||||||
alignment: MemoryLayout<UInt8>.alignment
|
|
||||||
)
|
|
||||||
pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(totalMemoryInGB))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setMoltenVKSettings() {
|
|
||||||
if let configs = loadSettings() {
|
|
||||||
self.config = configs
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.forEach { setting in
|
|
||||||
setenv(setting.string, setting.value, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Functions
|
|
||||||
func loadSettings() -> Ryujinx.Configuration? {
|
|
||||||
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
|
|
||||||
let data = jsonString.data(using: .utf8) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
|
|
||||||
} catch {
|
|
||||||
print("Failed to load settings: \(error)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
//
|
|
||||||
// GameListView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
// MARK: - This will most likely not be used in prod
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct GameListView: View {
|
|
||||||
@Binding var startemu: URL?
|
|
||||||
@State private var games: [URL] = []
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List(games, id: \.self) { game in
|
|
||||||
Button {
|
|
||||||
startemu = game
|
|
||||||
} label: {
|
|
||||||
Text(game.lastPathComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Games")
|
|
||||||
.onAppear(perform: loadGames)
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load games only from "roms" folder
|
|
||||||
do {
|
|
||||||
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
|
|
||||||
games = files
|
|
||||||
} catch {
|
|
||||||
print("Error loading games from roms folder: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// VulkanSDLView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import MetalKit
|
|
||||||
import SDL2
|
|
||||||
|
|
||||||
class SDLView: UIView {
|
|
||||||
var sdlwin: OpaquePointer?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
makeSDLWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
makeSDLWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWindowFlags() -> UInt32 {
|
|
||||||
return SDL_WINDOW_VULKAN.rawValue
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeSDLWindow() {
|
|
||||||
let width: Int32 = 1280 // Replace with the desired width
|
|
||||||
let height: Int32 = 720 // Replace with the desired height
|
|
||||||
|
|
||||||
let defaultFlags: UInt32 = SDL_WINDOW_SHOWN.rawValue
|
|
||||||
let fullscreenFlag: UInt32 = SDL_WINDOW_FULLSCREEN.rawValue // Or SDL_WINDOW_FULLSCREEN_DESKTOP if needed
|
|
||||||
|
|
||||||
// Create the SDL window
|
|
||||||
sdlwin = SDL_CreateWindow(
|
|
||||||
"Ryujinx",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
defaultFlags | getWindowFlags() // | fullscreenFlag | getWindowFlags()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if we successfully retrieved the SDL window
|
|
||||||
guard sdlwin != nil else {
|
|
||||||
print("Error creating SDL window: \(String(cString: SDL_GetError()))")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
print("SDL window created successfully.")
|
|
||||||
|
|
||||||
// Position SDL window over this UIView
|
|
||||||
self.syncSDLWindowPosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func syncSDLWindowPosition() {
|
|
||||||
guard let sdlwin = sdlwin else { return }
|
|
||||||
|
|
||||||
|
|
||||||
// Get the frame of the UIView in screen coordinates
|
|
||||||
let viewFrameInWindow = self.convert(self.bounds, to: nil)
|
|
||||||
|
|
||||||
// Set the SDL window position and size to match the UIView frame
|
|
||||||
SDL_SetWindowPosition(sdlwin, Int32(viewFrameInWindow.origin.x), Int32(viewFrameInWindow.origin.y))
|
|
||||||
SDL_SetWindowSize(sdlwin, Int32(viewFrameInWindow.width), Int32(viewFrameInWindow.height))
|
|
||||||
|
|
||||||
// Bring SDL window to the front
|
|
||||||
SDL_RaiseWindow(sdlwin)
|
|
||||||
|
|
||||||
print("SDL window positioned over SDLView.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
// Adjust SDL window whenever layout changes
|
|
||||||
syncSDLWindowPosition()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// VulkanSDLViewRepresentable.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import SwiftUI
|
|
||||||
import SDL2
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
struct SDLViewRepresentable: UIViewRepresentable {
|
|
||||||
let configure: (UInt32) -> Void
|
|
||||||
func makeUIView(context: Context) -> SDLView {
|
|
||||||
// Configure (start ryu) before initialsing SDLView so SDLView can get the SDL_Window from Ryu
|
|
||||||
let view = SDLView(frame: .zero)
|
|
||||||
configure(SDL_GetWindowID(view.sdlwin))
|
|
||||||
return view
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: SDLView, context: Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 25/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SettingsView: View {
|
|
||||||
@Binding var config: Ryujinx.Configuration
|
|
||||||
@Binding var MoltenVKSettings: [MoltenVKSettings]
|
|
||||||
|
|
||||||
var memoryManagerModes = [
|
|
||||||
("HostMapped", "Host (fast)"),
|
|
||||||
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
|
|
||||||
("SoftwarePageTable", "Software (slow)"),
|
|
||||||
]
|
|
||||||
|
|
||||||
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
|
|
||||||
|
|
||||||
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView {
|
|
||||||
VStack {
|
|
||||||
Section(header: Title("Graphics and Performance")) {
|
|
||||||
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
|
|
||||||
Toggle("Disable V-Sync", isOn: $config.disableVSync)
|
|
||||||
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
|
|
||||||
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
|
|
||||||
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
|
|
||||||
Resolution(value: $config.resscale)
|
|
||||||
Toggle("Enable Metal HUD", isOn: $metalHUDEnabled)
|
|
||||||
.onChange(of: metalHUDEnabled) { newValue in
|
|
||||||
if newValue {
|
|
||||||
MTLHud.shared.enable()
|
|
||||||
} else {
|
|
||||||
MTLHud.shared.disable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Title("Input Settings")) {
|
|
||||||
Toggle("List Input IDs", isOn: $config.listinputids)
|
|
||||||
Toggle("Nintendo Controller Layout", isOn: $config.nintendoinput)
|
|
||||||
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo)
|
|
||||||
// Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory)
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Title("Logging Settings")) {
|
|
||||||
Toggle("Enable Debug Logs", isOn: $config.debuglogs)
|
|
||||||
Toggle("Enable Trace Logs", isOn: $config.tracelogs)
|
|
||||||
}
|
|
||||||
Section(header: Title("CPU Mode")) {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Picker("Memory Manager Mode", selection: $config.memoryManagerMode) {
|
|
||||||
ForEach(memoryManagerModes, id: \.0) { key, displayName in
|
|
||||||
Text(displayName).tag(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(MenuPickerStyle()) // Dropdown style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Section(header: Title("Additional Settings")) {
|
|
||||||
//TextField("Game Path", text: $config.gamepath)
|
|
||||||
|
|
||||||
Text("PageSize \(String(Int(getpagesize())))")
|
|
||||||
|
|
||||||
TextField("Additional Arguments", text: Binding(
|
|
||||||
get: {
|
|
||||||
config.additionalArgs.joined(separator: ", ")
|
|
||||||
},
|
|
||||||
set: { newValue in
|
|
||||||
config.additionalArgs = newValue.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
if let configs = loadSettings() {
|
|
||||||
self.config = configs
|
|
||||||
print(configs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Settings")
|
|
||||||
.navigationBarItems(trailing: Button("Save") {
|
|
||||||
saveSettings()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveSettings() {
|
|
||||||
do {
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
encoder.outputFormatting = .prettyPrinted // Optional: Makes the JSON easier to read
|
|
||||||
let data = try encoder.encode(config)
|
|
||||||
let jsonString = String(data: data, encoding: .utf8)
|
|
||||||
|
|
||||||
// Save to UserDefaults
|
|
||||||
UserDefaults.standard.set(jsonString, forKey: "config")
|
|
||||||
|
|
||||||
print("Settings saved successfully!")
|
|
||||||
} catch {
|
|
||||||
print("Failed to save settings: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Resolution: View {
|
|
||||||
@Binding var value: Float
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Text("Resolution Scale (Custom):")
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
if value > 0.1 { // Prevent values going below 0.1
|
|
||||||
value -= 0.10
|
|
||||||
value = round(value * 1000) / 1000 // Round to two decimal places
|
|
||||||
}
|
|
||||||
print(value)
|
|
||||||
}) {
|
|
||||||
Text("-")
|
|
||||||
.frame(width: 30, height: 30)
|
|
||||||
.background(Color.gray.opacity(0.2))
|
|
||||||
.cornerRadius(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField("", value: $value, formatter: NumberFormatter.floatFormatter)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.frame(width: 60)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
.keyboardType(.decimalPad)
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
value += 0.10
|
|
||||||
value = round(value * 1000) / 1000 // Round to two decimal places
|
|
||||||
print(value)
|
|
||||||
}) {
|
|
||||||
Text("+")
|
|
||||||
.frame(width: 30, height: 30)
|
|
||||||
.background(Color.gray.opacity(0.2))
|
|
||||||
.cornerRadius(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NumberFormatter {
|
|
||||||
static var floatFormatter: NumberFormatter {
|
|
||||||
let formatter = NumberFormatter()
|
|
||||||
formatter.numberStyle = .decimal
|
|
||||||
formatter.maximumFractionDigits = 2
|
|
||||||
formatter.minimumFractionDigits = 2
|
|
||||||
formatter.allowsFloats = true
|
|
||||||
return formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Title: View {
|
|
||||||
let string: String
|
|
||||||
|
|
||||||
init(_ string: String) {
|
|
||||||
self.string = string
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Text(string)
|
|
||||||
.font(.title2)
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
11
src/MeloNX/dotnet.xcconfig.example
Normal file
11
src/MeloNX/dotnet.xcconfig.example
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// dotnet.xcconfig
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by June P on 12/25/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Configuration settings file format documentation can be found at:
|
||||||
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
|
DOTNET_PATH = $(HOME)/.dotnet/dotnet
|
@ -51,6 +51,59 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using ARMeilleure.Translation;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using Ryujinx.Audio.Backends.Dummy;
|
||||||
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
|
using Ryujinx.Audio.Backends.SoundIo;
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Ava.Common;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Input;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Ava.UI.Renderer;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ava.UI.Windows;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Configuration.Multiplayer;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.SystemInterop;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Ryujinx.HLE;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
|
using Ryujinx.Input;
|
||||||
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using Ryujinx.Ui.Common;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using SPB.Graphics.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
@ -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)
|
||||||
|
@ -63,8 +63,16 @@ namespace Ryujinx.Common.Configuration
|
|||||||
{
|
{
|
||||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
}
|
}
|
||||||
|
string userProfilePath;
|
||||||
|
if (OperatingSystem.IsIOS())
|
||||||
|
{
|
||||||
|
userProfilePath = appDataPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
||||||
|
}
|
||||||
|
|
||||||
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
|
||||||
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
||||||
|
|
||||||
if (Directory.Exists(portablePath))
|
if (Directory.Exists(portablePath))
|
||||||
|
@ -6,6 +6,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvAddressSpace : IDisposable
|
class HvAddressSpace : IDisposable
|
||||||
{
|
{
|
||||||
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));
|
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));
|
||||||
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvAddressSpaceRange : IDisposable
|
class HvAddressSpaceRange : IDisposable
|
||||||
{
|
{
|
||||||
private const ulong AllocationGranule = 1UL << 14;
|
private const ulong AllocationGranule = 1UL << 14;
|
||||||
|
@ -265,9 +265,10 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
static partial class HvApi
|
static partial class HvApi
|
||||||
{
|
{
|
||||||
public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor";
|
public const string LibraryName = "Hypervisor.framework/Hypervisor";
|
||||||
|
|
||||||
[LibraryImport(LibraryName, SetLastError = true)]
|
[LibraryImport(LibraryName, SetLastError = true)]
|
||||||
public static partial HvResult hv_vm_get_max_vcpu_count(out uint max_vcpu_count);
|
public static partial HvResult hv_vm_get_max_vcpu_count(out uint max_vcpu_count);
|
||||||
|
@ -4,6 +4,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvCpuContext : ICpuContext
|
class HvCpuContext : ICpuContext
|
||||||
{
|
{
|
||||||
private readonly ITickSource _tickSource;
|
private readonly ITickSource _tickSource;
|
||||||
|
@ -3,6 +3,7 @@ using System.Runtime.Versioning;
|
|||||||
|
|
||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
public class HvEngine : ICpuEngine
|
public class HvEngine : ICpuEngine
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvExecutionContext : IExecutionContext
|
class HvExecutionContext : IExecutionContext
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -7,6 +7,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvExecutionContextVcpu : IHvExecutionContext
|
class HvExecutionContextVcpu : IHvExecutionContext
|
||||||
{
|
{
|
||||||
private static readonly MemoryBlock _setSimdFpRegFuncMem;
|
private static readonly MemoryBlock _setSimdFpRegFuncMem;
|
||||||
|
@ -5,6 +5,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
readonly struct HvMemoryBlockAllocation : IDisposable
|
readonly struct HvMemoryBlockAllocation : IDisposable
|
||||||
{
|
{
|
||||||
private readonly HvMemoryBlockAllocator _owner;
|
private readonly HvMemoryBlockAllocator _owner;
|
||||||
|
@ -4,6 +4,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block>
|
class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block>
|
||||||
{
|
{
|
||||||
public class Block : PrivateMemoryAllocator.Block
|
public class Block : PrivateMemoryAllocator.Block
|
||||||
|
@ -16,6 +16,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
|
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||||
{
|
{
|
||||||
public const int PageBits = 12;
|
public const int PageBits = 12;
|
||||||
|
@ -4,6 +4,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
unsafe class HvVcpu
|
unsafe class HvVcpu
|
||||||
{
|
{
|
||||||
private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms
|
private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms
|
||||||
|
@ -5,6 +5,7 @@ using System.Threading;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
class HvVcpuPool
|
class HvVcpuPool
|
||||||
{
|
{
|
||||||
// Since there's a limit on the number of VCPUs we can create,
|
// Since there's a limit on the number of VCPUs we can create,
|
||||||
|
@ -5,6 +5,7 @@ using System.Runtime.Versioning;
|
|||||||
namespace Ryujinx.Cpu.AppleHv
|
namespace Ryujinx.Cpu.AppleHv
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
static class HvVm
|
static class HvVm
|
||||||
{
|
{
|
||||||
// This alignment allows us to use larger blocks on the page table.
|
// This alignment allows us to use larger blocks on the page table.
|
||||||
|
@ -10,6 +10,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
|
[SupportedOSPlatform("ios")]
|
||||||
static partial class TimeApi
|
static partial class TimeApi
|
||||||
{
|
{
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
[LibraryImport("libc", SetLastError = true)]
|
||||||
|
@ -10,7 +10,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
class NoWxCache : IDisposable
|
class NoWxCache : IDisposable
|
||||||
{
|
{
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int SharedCacheSize = 2047 * 1024 * 1024;
|
private const int SharedCacheSize = 192 * 1024 * 1024;
|
||||||
private const int LocalCacheSize = 128 * 1024 * 1024;
|
private const int LocalCacheSize = 128 * 1024 * 1024;
|
||||||
|
|
||||||
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
@ -313,7 +313,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
lock (_queueLock)
|
lock (_queueLock)
|
||||||
{
|
{
|
||||||
_api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
|
Result result = _api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe());
|
||||||
|
|
||||||
|
if (result != Result.Success)
|
||||||
|
{
|
||||||
|
|
||||||
|
Console.WriteLine($"QueueSubmit failed with error: {result}");
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user