1
0
forked from MeloNX/MeloNX

Compare commits

..

101 Commits

Author SHA1 Message Date
Stossy11
27aaea0d68 crazy 2025-01-15 14:23:52 +11:00
Stossy11
994f6c0732 Fix up some stuff 2025-01-15 14:22:56 +11:00
Stossy11
c5131d9850 Make project easier to navigate 2025-01-11 10:55:12 +11:00
Stossy11
09a757c445 Edit Settings, Make Loading View hide after game has loaded 2025-01-10 20:55:06 +11:00
Stossy11
71551adf2d make fullscreen fill the screen and disable "exclusive-fullscreen" as its not needed 2025-01-10 13:22:36 +11:00
Stossy11
d13dc50a10 Fix Super Mario Oddessy 2025-01-08 23:22:15 +11:00
Stossy11
2901f462aa Fix Mario Kart 8 Crash and SMO Glitching Issues :3 2025-01-07 13:20:13 +11:00
Tech Guy
160a58e127 crazy crap fixer 2025-01-05 08:16:54 +00:00
Tech Guy
9eae1ab594 Add open game from system(W.I.P) and Entitlements/ram view 2025-01-05 08:02:32 +00:00
Stossy11
d2e406fa56 crazy 2025-01-05 11:37:16 +11:00
June
054cb50b22 Allow easy changing of dotnet configurations 2024-12-25 09:10:16 +09:00
June
05b131b33f maintenence 2024-12-25 03:37:37 +09:00
Stossy11
ccf89aa324 Random changes to try and lower memory 2024-12-24 17:13:46 +11:00
Stossy11
ace6616067 Fix Memory Crash 2024-12-23 20:27:58 +11:00
Stossy11
0968360e08 Add a bunch of features 2024-12-23 16:34:14 +11:00
Stossy11
81941f9e9f Remove TrollStore and add TrollStore SVG 2024-12-21 16:10:38 +11:00
Tech Guy
6e7e5dbfca Merge remote-tracking branch 'refs/remotes/origin/XC-ios-ht' 2024-12-20 19:08:54 +00:00
Tech Guy
e76e927b28 add icon and fix typo 2024-12-20 19:07:14 +00:00
Tech Guy
b6bad055a8 add icon and fix typo 2024-12-20 18:56:53 +00:00
Catsoftware
2fbe6eb9da Add Trollstore JIT 2024-12-20 18:44:35 +00:00
Stossy11
86c93fe163 Fix crash when not attached (idk) 2024-12-20 19:41:20 +11:00
Stossy11
2e6e4eb2a0 Add Exit Emulation (broken) 2024-12-19 12:43:20 +11:00
Stossy11
438c1a896f Add Mii Maker (and try and fail to add the Software controller) 2024-12-19 10:31:56 +11:00
Stossy11
c5736f9b15 Lower Memory Usage 2024-12-19 08:16:40 +11:00
Stossy11
1662bcbc96 Add Show MeloNX folder 2024-12-18 19:24:43 +11:00
Stossy11
63427eb744 Add Installing Firmware 2024-12-18 19:11:37 +11:00
Stossy11
06f3c6d20e Add Unselected Controller menu to Settings 2024-12-18 08:26:47 +11:00
Stossy11
3e657d7229 SettingsView changes 2024-12-17 19:30:28 +11:00
Stossy11
ec16e150f6 Set MaxActiveMetalCommandBuffersPerQueue to 128 and ensure that Null Descriptors is set to false 2024-12-17 11:39:57 +11:00
Stossy11
aca5ee8305 Edit MoltenVK settings, force rotate to horizontal, edit settings page 2024-12-15 12:06:01 +11:00
Stossy11
a61e2a3992 Add Controller View into Settings and more 2024-12-10 18:20:30 +11:00
1735216de6 Merge pull request 'Settings' (#3) from 0-blu-ui-stuff into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#3
2024-12-10 06:34:43 +00:00
20547bc412 Merge branch 'XC-ios-ht' into 0-blu-ui-stuff 2024-12-10 06:34:33 +00:00
Stossy11
ed027f1649 Fix Variable Name 2024-12-10 15:57:25 +11:00
Stossy11
e02037d9c3 Fix crash with if image data is over 4mb and other VK changes 2024-12-10 15:48:28 +11:00
0-Blu
e74ab3a602 Part 1 - Settings 2024-12-09 21:38:49 +00:00
TechGuy
7025c32c4a Update Compile.md 2024-12-09 21:37:57 +00:00
Stossy11
de19cc29d8 Up Image Limit 2024-12-10 06:13:11 +11:00
Stossy11
f55d596688 Make DRM Check be a prompt 2024-12-09 22:25:37 +11:00
Stossy11
209d0f1a15 Fix iOS 16 and add support for 15 2024-12-09 21:21:13 +11:00
Stossy11
db86daef39 Fix DRM Check 2024-12-09 20:32:21 +11:00
Stossy11
9e09cb5767 Fix Resolution Scale 2024-12-09 20:29:06 +11:00
b089fda22d adjust sdl resolution to match device display. (sdl or games forces 16:9 anyways) 2024-12-09 00:51:31 -08:00
Stossy11
94dc643f26 Add Game Icon 2024-12-09 19:18:37 +11:00
Stossy11
e81ee8f8bf New UI Interface 2024-12-09 18:17:58 +11:00
Stossy11
fdbcc483b3 Add Game names instead of filenames and remove "disable vsync" option 2024-12-09 10:40:46 +11:00
Stossy11
5163737886 Update MoltenVK and setup Metal Argument Buffers 2024-12-09 09:06:44 +11:00
Stossy11
6a45d469db Fix flashing issue in games 2024-12-08 23:02:02 +11:00
Stossy11
658bdd7bec Fix Virtual Controller 2024-12-08 22:12:19 +11:00
Stossy11
d64ef5eed9 Remoce Swift SDL2 2024-12-08 20:04:28 +11:00
Stossy11
11c3d31764 Add Virtual Controller from Pomelo 2024-12-08 20:02:53 +11:00
Stossy11
61344e731e Clear MeloID 2024-12-07 15:05:19 +11:00
Stossy11
ddcb7a8f77 Add DRM Check 2024-12-07 15:03:14 +11:00
Stossy11
531446a6ce
Set Xcode version to 15 2024-12-07 01:14:58 +11:00
Stossy11
249e7104f6
Update project.pbxproj 2024-12-07 01:10:44 +11:00
Stossy11
3e0c86b047 Make it Xcode 14 or 15 2024-12-07 01:05:52 +11:00
Stossy11
51a2dfd27d Set Xcode version to 15 2024-12-07 00:40:44 +11:00
Stossy11
31b10799a3 Lower mem usage and other changes 2024-12-07 00:08:01 +11:00
Stossy11
11ec203e9f
Update Compile.md 2024-12-06 23:56:29 +11:00
Stossy11
de6c0a43b0
Undo "Remove increase memory limit from guide" 2024-12-05 13:17:37 +11:00
Tech Guy
663ec73028
Remove increase memory limit from guide 2024-12-04 22:15:08 +00:00
Stossy11
aed7a06f0d
Merge pull request #2 from MeloNX-Emu/Update-Compile-MD
Update Compile.md
2024-12-05 09:06:17 +11:00
Bentheminernz
e0785922d5
Update Compile.md 2024-12-05 11:02:43 +13:00
Tech Guy
abcad02f3e
Make the Compile Guide 2024-12-03 21:52:47 +00:00
a5a543f06c Re-introduce optimizations. (this will increase app size a little, please push release builds for ipa) 2024-12-02 23:18:48 -08:00
Stossy11
c000541be1 FIx JIT Popup 2024-12-02 23:49:06 +11:00
Stossy11
4149c329ea Lower active metal buffers to 64 2024-12-02 22:52:27 +11:00
Stossy11
300efe5f55 Add JIT detection and Lower memory plus other changes 2024-12-02 22:49:35 +11:00
Stossy11
bb4e7314a5 Fix onscreen controller and optimize stuff 2024-12-01 22:38:05 +11:00
Stossy11
73f14cf59c Add icon and fix crashes 2024-12-01 11:06:05 +11:00
Stossy11
464f14f143 Quick changes 2024-12-01 08:38:50 +11:00
Stossy11
860d4d363d
remove correct-ons-controller 2024-11-29 16:26:51 +11:00
Stossy11
c419381e63 Set Release 2024-11-29 00:06:37 +11:00
Stossy11
9a86b2000a Add On-Screen controller and Fix Internet and Lan Multiplayer 2024-11-29 00:02:41 +11:00
Tech Guy
b2424a9652
Update README.md 2024-11-27 18:02:27 +00:00
Tech Guy
1d70417281
Update README.md 2024-11-27 18:02:07 +00:00
Tech Guy
95da853b7f
Update README.md 2024-11-27 18:01:56 +00:00
Stossy11
a14aadf878 Add MetalHUD, Resolution Controls, change default settings 2024-11-27 07:53:33 +11:00
Stossy11
45e2785e93 Add more iOS Checks 2024-11-26 09:10:09 +11:00
Stossy11
d3031752be Update Settings Page and Edit MoltenVK Configuration 2024-11-26 00:14:53 +11:00
Stossy11
c4c71a4cb6 add SIGBUS breakpoint 2024-11-25 19:55:59 +11:00
Stossy11
8e229fe454 Fix config 2024-11-25 10:14:40 +11:00
Stossy11
e02927cadb Add Config Editor 2024-11-25 09:49:10 +11:00
Stossy11
098467f3f3 Add HStack to fix controller UI 2024-11-25 06:46:48 +11:00
Stossy11
8ebde3f921 Fix One Window Crash 2024-11-25 06:37:47 +11:00
Stossy11
ab28f9a24a Add Controller Selector and Update MoltenVK 2024-11-25 06:31:38 +11:00
Tech Guy
674824184a
Update README.md 2024-11-24 18:40:46 +00:00
1bc0d3dba9 Comment out debug info and optimize for iOS 2024-11-24 04:21:02 -08:00
stossy11
0a3b4f71a9 Fix Descriptor Issues 2024-11-24 11:33:19 +11:00
stossy11
b9163f9fde Bring back old changes and enable Metal Argument Buffers 2024-11-23 22:20:59 +11:00
stossy11
05d1730c17 Start Ryujinx code from scratch 2024-11-23 17:31:54 +11:00
stossy11
e170ed01ad Reset Ryujinx 2024-11-23 15:16:06 +11:00
stossy11
8d4f004a59 Edit JIT Cache size and disable null descriptors 2024-11-23 13:09:51 +11:00
stossy11
437f7f8c04 changes 2024-11-23 09:53:24 +11:00
2ba59b2ce9 Update packages and fix descriptor address issue 2024-11-11 22:30:25 -08:00
Tech Guy
d9099429f2
Update README.md 2024-11-03 06:41:32 +00:00
Tech Guy
bea7528eb6
Update README.md 2024-11-03 06:41:05 +00:00
stossy11
05880cc8a5 Create the window in swift again 2024-11-03 17:09:04 +11:00
stossy11
10e45533e1 Get SDL Window Automatically 2024-11-03 15:09:01 +11:00
stossy11
b86e3301bb Add Logs 2024-11-03 13:32:35 +11:00
stossy11
165bb0c5d2 Complete Rewrite 2024-11-03 13:21:00 +11:00
690 changed files with 102458 additions and 2189 deletions

4
.gitignore vendored
View File

@ -10,6 +10,8 @@
# Build results # Build results
dotnet.xcconfig
[Dd]ebug/ [Dd]ebug/
[Rr]elease/ [Rr]elease/
x64/ x64/
@ -173,4 +175,4 @@ PublishProfiles/
# Glade backup files # Glade backup files
*.glade~ *.glade~
MeloNX-XC/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib

33
Compile.md Normal file
View File

@ -0,0 +1,33 @@
# How to compile MeloNX using macOS
## Prerequisites
- [.NET 8.0](<https://dotnet.microsoft.com/en-us/download/dotnet/8.0>)
- A computer with macOS
## Compiling
1. Clone the Git Repo and build Ryujinx
```
git clone https://github.com/melonx-emu/melonx/tree/XC-ios-ht
cd melonx
./compile.sh -x
```
2. Open the Xcode project, stored at MeloNX/src/MeloNX
3. Make sure `Ryujinx.SDL2.Headless.dylib` is set to `Embed & Sign` in the General settings for the Xcode project
4. Signing & Capabilities
Change your 'Team' to your Developer Account (free or paid) and change Bundle Identifier to
`com.*your name*.MeloNX`
6. Build and Run
`CMD+R`
7. Check the [post-setup guide](<https://github.com/melonx-emu/melonx/tree/XC-ios-ht/postsetup.md>)
## If you don't have a paid developer account
Make sure these entitlements are removed if you don't have a paid Apple Developer account
```
Extended Virtual Addressing
Increased Debugging Memory Limit
```

View File

@ -33,15 +33,15 @@
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" /> <PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" /> <PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.3" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" /> <PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" /> <PackageVersion Include="SPB" Version="0.0.4-build28" />

View File

@ -1,499 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
4E87E2F32CCE315100F54325 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E87E2F22CCE315100F54325 /* SDL */; };
4E87E2F62CCE33B500F54325 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E87E2F52CCE33B500F54325 /* GameController.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
4E87E2CA2CCE2D8E00F54325 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4E87E28C2CCE2C1000F54325 /* MeloNX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeloNX.app; sourceTree = BUILT_PRODUCTS_DIR; };
4E87E2F52CCE33B500F54325 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
4E87E3022CCE3E0700F54325 /* Exceptions for "MeloNX" folder in "MeloNX" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 4E87E28B2CCE2C1000F54325 /* MeloNX */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
4E87E2CB2CCE2D8E00F54325 /* Exceptions for "MeloNX" folder in "Embed Frameworks" phase from "MeloNX" target */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = {
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (CodeSignOnCopy, );
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/System_Globalization_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/System_IO_Compression_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/System_IO_Ports_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/System_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/System_Net_Security_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/System_Security_Cryptography_Native_Apple.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/clrjit.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/coreclr.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
};
buildPhase = 4E87E2CA2CCE2D8E00F54325 /* Embed Frameworks */;
membershipExceptions = (
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
Dependencies/XCFrameworks/clrjit.xcframework,
Dependencies/XCFrameworks/coreclr.xcframework,
Dependencies/XCFrameworks/MoltenVK.xcframework,
Dependencies/XCFrameworks/SDL2.xcframework,
Dependencies/XCFrameworks/System_Globalization_Native.xcframework,
Dependencies/XCFrameworks/System_IO_Compression_Native.xcframework,
Dependencies/XCFrameworks/System_IO_Ports_Native.xcframework,
Dependencies/XCFrameworks/System_Native.xcframework,
Dependencies/XCFrameworks/System_Net_Security_Native.xcframework,
Dependencies/XCFrameworks/System_Security_Cryptography_Native_Apple.xcframework,
);
};
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4E87E28E2CCE2C1000F54325 /* MeloNX */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4E87E3022CCE3E0700F54325 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
4E87E2CB2CCE2D8E00F54325 /* Exceptions for "MeloNX" folder in "Embed Frameworks" phase from "MeloNX" target */,
);
path = MeloNX;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
4E87E2892CCE2C1000F54325 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4E87E2F62CCE33B500F54325 /* GameController.framework in Frameworks */,
4E87E2F32CCE315100F54325 /* SDL in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4E87E2832CCE2C1000F54325 = {
isa = PBXGroup;
children = (
4E87E28E2CCE2C1000F54325 /* MeloNX */,
4E87E2F42CCE33B500F54325 /* Frameworks */,
4E87E28D2CCE2C1000F54325 /* Products */,
);
sourceTree = "<group>";
};
4E87E28D2CCE2C1000F54325 /* Products */ = {
isa = PBXGroup;
children = (
4E87E28C2CCE2C1000F54325 /* MeloNX.app */,
);
name = Products;
sourceTree = "<group>";
};
4E87E2F42CCE33B500F54325 /* Frameworks */ = {
isa = PBXGroup;
children = (
4E87E2F52CCE33B500F54325 /* GameController.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
4E87E28B2CCE2C1000F54325 /* MeloNX */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4E87E29A2CCE2C1100F54325 /* Build configuration list for PBXNativeTarget "MeloNX" */;
buildPhases = (
4ECF0AD52CD0FB5B00A3820B /* ShellScript */,
4E87E2882CCE2C1000F54325 /* Sources */,
4E87E2892CCE2C1000F54325 /* Frameworks */,
4E87E28A2CCE2C1000F54325 /* Resources */,
4E87E2CA2CCE2D8E00F54325 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
4E87E28E2CCE2C1000F54325 /* MeloNX */,
);
name = MeloNX;
packageProductDependencies = (
4E87E2F22CCE315100F54325 /* SDL */,
);
productName = MeloNX;
productReference = 4E87E28C2CCE2C1000F54325 /* MeloNX.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
4E87E2842CCE2C1000F54325 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1600;
TargetAttributes = {
4E87E28B2CCE2C1000F54325 = {
CreatedOnToolsVersion = 16.0;
};
};
};
buildConfigurationList = 4E87E2872CCE2C1000F54325 /* Build configuration list for PBXProject "MeloNX" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 4E87E2832CCE2C1000F54325;
minimizedProjectReferenceProxies = 1;
packageReferences = (
4E87E2F12CCE315100F54325 /* XCRemoteSwiftPackageReference "SwiftSDL2" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = 4E87E28D2CCE2C1000F54325 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
4E87E28B2CCE2C1000F54325 /* MeloNX */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
4E87E28A2CCE2C1000F54325 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4ECF0AD52CD0FB5B00A3820B /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/bash;
shellScript = "
";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
4E87E2882CCE2C1000F54325 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
4E87E2982CCE2C1100F54325 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
4E87E2992CCE2C1100F54325 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
4E87E29B2CCE2C1100F54325 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(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",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Header/MeloNX-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
4E87E29C2CCE2C1100F54325 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(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",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Header/MeloNX-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
4E87E2872CCE2C1000F54325 /* Build configuration list for PBXProject "MeloNX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4E87E2982CCE2C1100F54325 /* Debug */,
4E87E2992CCE2C1100F54325 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4E87E29A2CCE2C1100F54325 /* Build configuration list for PBXNativeTarget "MeloNX" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4E87E29B2CCE2C1100F54325 /* Debug */,
4E87E29C2CCE2C1100F54325 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
4E87E2F12CCE315100F54325 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ctreffs/SwiftSDL2";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.4.1;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
4E87E2F22CCE315100F54325 /* SDL */ = {
isa = XCSwiftPackageProductDependency;
package = 4E87E2F12CCE315100F54325 /* XCRemoteSwiftPackageReference "SwiftSDL2" */;
productName = SDL;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 4E87E2842CCE2C1000F54325 /* Project object */;
}

View File

@ -1,15 +0,0 @@
{
"originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5",
"pins" : [
{
"identity" : "swiftsdl2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ctreffs/SwiftSDL2",
"state" : {
"revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a",
"version" : "1.4.1"
}
}
],
"version" : 3
}

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "64A8AF27-6696-4D7A-8C62-06216A95ECF0"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "C2C839C6-26A1-468A-9479-A00FD57EA17C"
shouldBeEnabled = "Yes"
nameForDebugger = "ignore-sigusr1"
ignoreCount = "0"
continueAfterRunningActions = "Yes"
filePath = "MeloNX/Core/Ryujinx.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "111"
endingLineNumber = "111"
landmarkName = "startWithRunLoop(config:)"
landmarkType = "7">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "process handle SIGUSR1 -n true -p true -s false">
</ActionContent>
</BreakpointActionProxy>
</Actions>
<Locations>
<Location
uuid = "C2C839C6-26A1-468A-9479-A00FD57EA17C - e09e330dd17da5b1"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "MeloNX.RyujinxEmulator.startWithRunLoop(config: MeloNX.RyujinxEmulator.Configuration) throws -&gt; ()"
moduleName = "MeloNX.debug.dylib"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/stossy11/MeloNX/MeloNX-XC/MeloNX/Core/Ryujinx.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "111"
endingLineNumber = "111">
</Location>
<Location
uuid = "C2C839C6-26A1-468A-9479-A00FD57EA17C - a8ffb78cb80274ea"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 @Sendable () -&gt; () in MeloNX.RyujinxEmulator.startWithRunLoop(config: MeloNX.RyujinxEmulator.Configuration) throws -&gt; ()"
moduleName = "MeloNX.debug.dylib"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/stossy11/MeloNX/MeloNX-XC/MeloNX/Core/Ryujinx.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "112"
endingLineNumber = "112">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "5A4DEE32-2E63-4404-BFDE-AD7F1337DA66"
shouldBeEnabled = "Yes"
nameForDebugger = "ignore-sigusr1"
ignoreCount = "0"
continueAfterRunningActions = "Yes"
filePath = "MeloNX/Core/Ryujinx.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "120"
endingLineNumber = "120"
landmarkName = "startWithRunLoop(config:)"
landmarkType = "7">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "process handle SIGUSR1 -n true -p true -s false">
</ActionContent>
</BreakpointActionProxy>
</Actions>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -1,168 +0,0 @@
//
// ContentView.swift
// MeloNX
//
// Created by Stossy11 on 27/10/2024.
//
import SwiftUI
import SDL2
import GameController
var theWindow: UIWindow? = nil
struct ContentView: View {
@State var device: MTLDevice? = MTLCreateSystemDefaultDevice()
@State var gameUrl: URL?
@State var showFileImporter: Bool = false
@State var emulationStarted: Bool = false
@State var mainThread: Bool = true
@State var debugmode: Int = 0
init() {
DispatchQueue.main.async {
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
}
}
var body: some View {
ZStack {
if let gameUrl, emulationStarted {
VulkanSDLViewRepresentable { // displayid in
let config = RyujinxEmulator.Configuration(
inputPath: gameUrl.path,
mainThread: mainThread,
graphicsBackend: "Vulkan",
additionalArgs: [
//"--display-id", String(displayid),
// "--fullscreen", "true"
]
)
showVirtualController(url: gameUrl, ryuconfig: config)
}
}
VStack {
Text("NX iOS")
.font(.largeTitle)
.onTapGesture {
debugmode += 1
}
.padding()
if debugmode > 9 {
Text("Debug Mode:")
.font(.title)
Text("Is on Main Thread?: \(mainThread)")
.font(.title2)
Toggle(isOn: $mainThread) {
Text("Use Main Thread")
}
}
Button {
showFileImporter.toggle()
} label: {
Text("Select Game")
}
if let gameUrl {
Button {
emulationStarted = true
} label: {
Text("Go!")
}
.padding(8)
}
}
}
.padding()
.fileImporter(isPresented: $showFileImporter, allowedContentTypes: [.item]) { result in
switch result {
case .success(let url):
gameUrl = url
case .failure(let err):
print(err.localizedDescription)
}
}
}
}
func startEmulation(game: URL, config: RyujinxEmulator.Configuration) {
setenv("DOTNET_EnableDiagnostics", "0", 1)
setenv("HOME", String(validatingUTF8: getenv("HOME"))! + "/Documents", 1)
setenv("MVK_CONFIG_LOG_LEVEL", "4", 1)
let config = config
patchMakeKeyAndVisible()
// SDL_Init(SDL_INIT_VIDEO)
DispatchQueue.main.async {
let emulator = RyujinxEmulator()
do {
try emulator.startWithRunLoop(config: config)
} catch {
print(error)
}
}
}
func patchMakeKeyAndVisible() {
DispatchQueue.main.async {
let uiwindowClass = UIWindow.self
let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible))!
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible))!
method_exchangeImplementations(m1, m2)
}
}
extension UIWindow {
@objc func wdb_makeKeyAndVisible() {
DispatchQueue.main.async {
print("Making window key and visible...")
if #available(iOS 13.0, *) {
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
}
self.wdb_makeKeyAndVisible()
theWindow = self
}
}
}
@available(iOS 15.0, *)
var g_gcVirtualController: GCVirtualController!
@available(iOS 15.0, *)
func showVirtualController(url: URL, ryuconfig: RyujinxEmulator.Configuration) {
DispatchQueue.main.async {
print("Showing virtual controller...")
let config = GCVirtualController.Configuration()
config.elements = [
GCInputDirectionalDpad, GCInputButtonA, GCInputButtonB, GCInputButtonX, GCInputButtonY,
]
g_gcVirtualController = GCVirtualController(configuration: config)
g_gcVirtualController.connect { err in
print("Controller connect: \(String(describing: err))")
DispatchQueue.main.async {
startEmulation(game: url, config: ryuconfig)
}
}
}
}
@available(iOS 15.0, *)
func reconnectVirtualController() {
print("Reconnecting virtual controller...")
g_gcVirtualController.disconnect()
DispatchQueue.main.async {
g_gcVirtualController.connect { err in
print("Reconnected: err \(String(describing: err))")
}
}
}

View File

@ -1,22 +0,0 @@
//
// Ryujinx.h
// MeloNX
//
// Created by Stossy11 on 1/11/2024.
//
#ifndef RyujinxSDL_h
#define RyujinxSDL_h
#ifdef __cplusplus
extern "C" {
#endif
// Declare the main_ryujinx_sdl function, matching the signature
int main_ryujinx_sdl(int argc, char **argv);
#ifdef __cplusplus
}
#endif
#endif /* RyujinxSDL_h */

View File

@ -1,368 +0,0 @@
//
// Ryujinx.swift
// MeloNX
//
// Created by Stossy11 on 27/10/2024.
//
import Foundation
import SwiftUI
enum RyujinxError: Error {
case libraryLoadError
case executionError(code: Int32)
case alreadyRunning
case notRunning
}
class RyujinxEmulator {
private var isRunning = false
private var emulationThread: Thread?
struct Configuration {
let inputPath: String
let mainThread: Bool // i don't know why i added this
let graphicsBackend: String
var additionalArgs: [String]
init(
inputPath: String,
mainThread: Bool = true,
graphicsBackend: String = "Vulkan",
additionalArgs: [String] = []
) {
self.inputPath = inputPath
self.mainThread = mainThread
self.graphicsBackend = graphicsBackend
self.additionalArgs = additionalArgs
}
}
private static func start(with config: Configuration) throws {
var args: [String] = []
// Taken from the POC
/*
var args: [String] = [
"--enable-debug-logs", "false", "--enable-trace-logs", "false", "--memory-manager-mode",
"SoftwarePageTable",
"--graphics-backend",
"Vulkan",
//"--enable-fs-integrity-checks", "false",
"--input-id-1", "0",
// "--list-inputs-ids", "true",
config.inputPath,
]
*/
args.append(config.inputPath)
args.append("--graphics-backend")
args.append(config.graphicsBackend)
// args.append(contentsOf: ["--memory-manager-mode", "SoftwarePageTable"])
// args.append(contentsOf: ["--fullscreen", "true"])
args.append(contentsOf: ["--enable-debug-logs", "true"])
args.append(contentsOf: ["--enable-trace-logs", "true"])
// args.append(contentsOf: ["--list-inputs-ids", "true"])
args.append(contentsOf: ["--input-id-1", "1-47150005-05ac-0000-0100-00004f066d01"])
// args.append("--input-path")
args.append(contentsOf: config.additionalArgs)
let cArgs = args.map { strdup($0) }
defer {
cArgs.forEach { ptr in
if let ptr = ptr {
free(ptr)
}
}
}
var argvPtrs = cArgs
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
if result != 0 {
throw RyujinxError.executionError(code: result)
}
}
// cray z
func startWithRunLoop(config: Configuration) throws {
guard !isRunning else {
throw RyujinxError.alreadyRunning
}
isRunning = true
DispatchQueue.main.async {
do {
try Self.start(with: config)
} catch {
Self.log("Emulation failed to start: \(error)")
self.isRunning = false
return
}
}
emulationThread = Thread {
let runLoop = RunLoop.current
let port = Port()
runLoop.add(port, forMode: .default)
print(config.mainThread ? "Running on the main thread" : "Running on the background thread")
/*
if config.mainThread {
DispatchQueue.main.async {
do {
try Self.start(with: config)
} catch {
Self.log("Emulation failed to start: \(error)")
self.isRunning = false
return
}
}
} else {
do {
try Self.start(with: config)
} catch {
Self.log("Emulation failed to start: \(error)")
self.isRunning = false
return
}
}
*/
while self.isRunning && runLoop.run(mode: .default, before: .distantFuture) {
autoreleasepool { }
}
Self.log("Emulation loop ended")
}
emulationThread?.name = "RyujinxEmulationThread"
emulationThread?.qualityOfService = .userInteractive
emulationThread?.threadPriority = 0.9
// emulationThread?.start()
}
func quickStart(romPath: String) throws {
let config = Configuration(inputPath: romPath)
try startWithRunLoop(config: config)
}
/// Stops the emulator
func stop() throws {
guard isRunning else {
throw RyujinxError.notRunning
}
isRunning = false
emulationThread?.cancel()
emulationThread = nil
}
var running: Bool {
return isRunning
}
static func log(_ message: String) {
print("[Ryujinx] \(message)")
}
}
extension RyujinxEmulator.Configuration {
var toCommandLineArgs: [String] {
var args: [String] = []
args.append(inputPath)
// if enableKeyboard {
// args.append("--enable-keyboard")
// }
args.append("--graphics-backend")
args.append(graphicsBackend)
args.append(contentsOf: additionalArgs)
return args
}
/// Create configuration from command line arguments
static func fromCommandLineArgs(_ args: [String]) -> RyujinxEmulator.Configuration? {
var inputPath: String?
var enableKeyboard = false
var graphicsBackend = "Vulkan"
var additionalArgs: [String] = []
var i = 0
while i < args.count {
switch args[i] {
case "--enable-keyboard":
enableKeyboard = true
case "--graphics-backend":
i += 1
if i < args.count {
graphicsBackend = args[i]
}
default:
additionalArgs.append(args[i])
}
i += 1
}
guard let inputPath = inputPath else {
return nil
}
return RyujinxEmulator.Configuration(
inputPath: inputPath,
mainThread: enableKeyboard,
graphicsBackend: graphicsBackend,
additionalArgs: additionalArgs
)
}
}
// MARK: - Code Taken from POC
var g_HookMmapReserved4GB: UnsafeMutableRawPointer! = nil
var g_HookMmapReservedJitCache: UnsafeMutableRawPointer! = nil
func initHookMmap() -> Bool {
// Hack: if out of memory, you can reserve less (e.g. around 0xc000_0000 or even 0x8000_0000) but it'll crash later
let reserve4GBSize = 0x1_0000_0000
g_HookMmapReserved4GB = mmap(
nil, reserve4GBSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
if g_HookMmapReserved4GB == MAP_FAILED {
print("can't allocate 4gb")
return false
}
let reserveJitCacheSize = 0x8000_0000
g_HookMmapReservedJitCache = mmap(
nil, reserveJitCacheSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
if g_HookMmapReservedJitCache == MAP_FAILED {
print("can't allocate jit cache")
return false
}
if !reallocateAreaWithOwnership(address: g_HookMmapReserved4GB, size: reserve4GBSize) {
print("can't reallocate area with ownership for 4gb")
return false
}
if !reallocateAreaWithOwnership(address: g_HookMmapReservedJitCache, size: reserveJitCacheSize) {
print("can't reallocate area with ownership for jitcache")
return false
}
print("Allocated Needed Ram")
return true
}
func hookMmap(
addr: UnsafeMutableRawPointer?, len: Int, prot: Int32, flags: Int32, fd: Int32, offset: off_t
) -> UnsafeMutableRawPointer! {
print("mmap hook! \(String(describing: addr)) \(len) \(prot) \(flags)")
// TODO(zhuowei): threads?
if g_HookMmapReserved4GB != nil && len == 0x1_0000_0000 {
let ret = g_HookMmapReserved4GB
g_HookMmapReserved4GB = nil
print("returning 4gb: \(ret!)")
return ret
}
if g_HookMmapReservedJitCache != nil && len == 0x7ff0_0000 {
// Hack: it wants 2GB; give it smaller
let ret = g_HookMmapReservedJitCache
g_HookMmapReservedJitCache = nil
print("returning jitcache: \(ret!)")
return ret
}
return mmap(addr, len, prot, flags, fd, offset)
}
func reallocateAreaWithOwnership(address: UnsafeMutableRawPointer, size: Int) -> Bool {
let addressBase: mach_vm_address_t = mach_vm_address_t(UInt(bitPattern: address))
let mapChunkSize = 128 * 1024 * 1024
for off in stride(from: 0, to: size, by: mapChunkSize) {
let targetSize = memory_object_size_t(min(mapChunkSize, size - off))
var memoryObjectSize = targetSize
var memoryObjectPort: mach_port_t = 0
let err = mach_make_memory_entry_64(
mach_task_self_, &memoryObjectSize, 0,
MAP_MEM_NAMED_CREATE | MAP_MEM_LEDGER_TAGGED | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
&memoryObjectPort, /*parent_entry=*/ 0)
if err != 0 {
print("mach_make_memory_entry_64 returned error: \(String(cString: mach_error_string(err)!))")
return false
}
defer { mach_port_deallocate(mach_task_self_, memoryObjectPort) }
if memoryObjectSize != targetSize {
print("size is wrong?! \(memoryObjectSize) \(targetSize)")
return false
}
let err2 = mach_memory_entry_ownership(
memoryObjectPort, TASK_NULL, VM_LEDGER_TAG_DEFAULT, VM_LEDGER_FLAG_NO_FOOTPRINT)
if err2 != 0 {
print(
"mach_memory_entry_ownership returned error: \(String(cString: mach_error_string(err2)!))")
return false
}
let targetMapAddress: vm_address_t = vm_address_t(addressBase) + vm_address_t(off)
var mapAddress = targetMapAddress
let err3 = vm_map(
mach_task_self_, &mapAddress, vm_size_t(memoryObjectSize), /*mask=*/ 0, /*flags=*/
VM_FLAGS_OVERWRITE,
memoryObjectPort, /*offset=*/ 0, /*copy=*/ 0, VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, VM_INHERIT_COPY)
if err3 != 0 {
print("vm_map returned error: \(String(cString: mach_error_string(err3)!))")
return false
}
if mapAddress != targetMapAddress {
print("map address wrong")
return false
}
}
return true
}
typealias SystemNative_Open_Type = @convention(c) (
_ path: UnsafePointer<CChar>, _ flags: Int32, _ mode: Int32
) -> Int
var real_SystemNative_Open: SystemNative_Open_Type!
func hook_SystemNative_Open(path: UnsafePointer<CChar>, flags: Int32, mode: Int32) -> Int {
let fileName = String(cString: path)
print("opening \(fileName)")
return real_SystemNative_Open(path, flags, mode)
}
func pInvokeOverride(libraryName: UnsafePointer<CChar>!, entrypointName: UnsafePointer<CChar>!)
-> UnsafeRawPointer?
{
let libraryName = String(cString: libraryName)
let entrypointName = String(cString: entrypointName)
// print(libraryName, entrypointName)
if entrypointName == "mmap" {
typealias MmapType = @convention(c) (
_: UnsafeMutableRawPointer?, _: Int, _: Int32, _: Int32, _: Int32, _: off_t
) -> UnsafeMutableRawPointer?
return unsafeBitCast(hookMmap as MmapType, to: UnsafeRawPointer.self)
} else if entrypointName == "SystemNative_Open" {
let handle = dlopen("libSystem.Native.dylib", RTLD_LOCAL | RTLD_LAZY)
real_SystemNative_Open = unsafeBitCast(
dlsym(handle, "SystemNative_Open"), to: SystemNative_Open_Type.self)
return unsafeBitCast(
hook_SystemNative_Open as SystemNative_Open_Type, to: UnsafeRawPointer.self)
}
return nil
}

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libSystem.Globalization.Native.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libSystem.IO.Compression.Native.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libSystem.IO.Ports.Native.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libSystem.Native.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libSystem.Net.Security.Native.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libSystem.Security.Cryptography.Native.Apple.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libclrjit.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libcoreclr.dylib</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -1,8 +0,0 @@
//
// MeloNX-Bridging-Header.h
// MeloNX
//
// Created by Stossy11 on 1/11/2024.
//
#import "../Core/Ryujinx.h"

View File

@ -1,17 +0,0 @@
//
// MeloNXApp.swift
// MeloNX
//
// Created by Stossy11 on 27/10/2024.
//
import SwiftUI
@main
struct MeloNXApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@ -1,297 +0,0 @@
//
// MetalVIew.swift
// MeloNX
//
// Created by Stossy11 on 27/10/2024.
//
import SwiftUI
import Metal
import MetalKit
import UIKit
import SDL2
struct VulkanSDLViewRepresentable: UIViewRepresentable {
let configure: () -> Void
func makeUIView(context: Context) -> VulkanSDLView {
configure()
let view = VulkanSDLView(frame: .zero)
return view
}
func updateUIView(_ uiView: VulkanSDLView, context: Context) {
// Handle any updates if needed
}
}
class VulkanSDLView: UIView {
var sdlWindow: OpaquePointer?
var metalView: UnsafeMutableRawPointer?
override init(frame: CGRect) {
super.init(frame: frame)
DispatchQueue.main.async { [self] in
initializeSDL()
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
DispatchQueue.main.async { [self] in
initializeSDL()
}
}
private func initializeSDL() {
// Initialize SDL with video support
// Create an SDL window with Metal support
DispatchQueue.main.async { [self] in
sdlWindow = SDL_GetWindowFromID(1)
}
guard sdlWindow != nil else {
print("Error creating SDL window: \(String(cString: SDL_GetError()))")
return
}
// Create SDL Metal view and attach to this UIView
DispatchQueue.main.async { [self] in
metalView = SDL_Metal_CreateView(sdlWindow)
if metalView == nil {
print("Failed to create SDL Metal view.")
return
}
}
DispatchQueue.main.async { [self] in
if let metalLayerPointer = SDL_Metal_GetLayer(metalView) {
let metalLayer = Unmanaged<CAMetalLayer>.fromOpaque(metalLayerPointer).takeUnretainedValue()
metalLayer.device = MTLCreateSystemDefaultDevice()
// metalLayer.pixelFormat = .bgra8Unorm
layer.addSublayer(metalLayer)
}
}
}
deinit {
if let metalView = metalView {
SDL_Metal_DestroyView(metalView)
}
if let sdlWindow = sdlWindow {
SDL_DestroyWindow(sdlWindow)
}
SDL_Quit()
}
}
struct MetalView: UIViewRepresentable {
let device: MTLDevice?
let configure: (UIView) -> Void
func makeUIView(context: Context) -> SudachiScreenView {
let view = SudachiScreenView()
configure(view.primaryScreen)
return view
}
func updateUIView(_ uiView: SudachiScreenView, context: Context) {
//
}
}
class SudachiScreenView: UIView {
var primaryScreen: UIView!
var portraitconstraints = [NSLayoutConstraint]()
var landscapeconstraints = [NSLayoutConstraint]()
var fullscreenconstraints = [NSLayoutConstraint]()
let userDefaults = UserDefaults.standard
override init(frame: CGRect) {
super.init(frame: frame)
if userDefaults.bool(forKey: "isfullscreen") {
// setupSudachiScreenforcools()
setupSudachiScreen2()
} else if userDefaults.bool(forKey: "isairplay") {
setupSudachiScreen2()
} else if userDefaults.bool(forKey: "169fullscreen") { // this is for the 16/9 aspect ratio full screen
setupSudachiScreenforcools()
} else if UIDevice.current.userInterfaceIdiom == .pad {
setupSudachiScreenforiPad()
} else {
setupSudachiScreen()
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
if userDefaults.bool(forKey: "isfullscreen") {
setupSudachiScreen2()
} else if userDefaults.bool(forKey: "isairplay") {
setupSudachiScreen2()
} else if UIDevice.current.userInterfaceIdiom == .pad {
setupSudachiScreenforiPad()
} else {
setupSudachiScreen()
}
}
func setupSudachiScreen2() {
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
primaryScreen.clipsToBounds = true
addSubview(primaryScreen)
fullscreenconstraints = [
primaryScreen.topAnchor.constraint(equalTo: topAnchor),
primaryScreen.leadingAnchor.constraint(equalTo: leadingAnchor),
primaryScreen.trailingAnchor.constraint(equalTo: trailingAnchor),
primaryScreen.bottomAnchor.constraint(equalTo: bottomAnchor)
]
addConstraints(fullscreenconstraints)
}
func setupSudachiScreenforcools() { // oh god this took a long time, im going insane
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
primaryScreen.clipsToBounds = true
addSubview(primaryScreen)
primaryScreen.layer.cornerRadius = 5
primaryScreen.layer.masksToBounds = true
NSLayoutConstraint.activate([
primaryScreen.centerXAnchor.constraint(equalTo: centerXAnchor),
primaryScreen.centerYAnchor.constraint(equalTo: centerYAnchor),
primaryScreen.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
primaryScreen.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
])
let aspectRatio: CGFloat = 16.0/9.0
let aspectRatioConstraint = NSLayoutConstraint(
item: primaryScreen ?? UIView(),
attribute: .width,
relatedBy: .equal,
toItem: primaryScreen,
attribute: .height,
multiplier: aspectRatio,
constant: 0
)
aspectRatioConstraint.priority = .required - 1
primaryScreen.addConstraint(aspectRatioConstraint)
let heightConstraint = primaryScreen.heightAnchor.constraint(equalTo: heightAnchor)
heightConstraint.priority = .defaultHigh
let widthConstraint = primaryScreen.widthAnchor.constraint(equalTo: widthAnchor)
widthConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([heightConstraint, widthConstraint])
// Make primaryScreen fill container
fullscreenconstraints = [
primaryScreen.topAnchor.constraint(equalTo: primaryScreen.topAnchor),
primaryScreen.bottomAnchor.constraint(equalTo: primaryScreen.bottomAnchor),
primaryScreen.leadingAnchor.constraint(equalTo: primaryScreen.leadingAnchor),
primaryScreen.trailingAnchor.constraint(equalTo: primaryScreen.trailingAnchor)
]
NSLayoutConstraint.activate(fullscreenconstraints)
}
func setupSudachiScreenforiPad() {
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
primaryScreen.clipsToBounds = true
primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor
primaryScreen.layer.borderWidth = 3
primaryScreen.layer.cornerCurve = .continuous
primaryScreen.layer.cornerRadius = 10
addSubview(primaryScreen)
portraitconstraints = [
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10),
primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10),
primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16),
]
landscapeconstraints = [
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 50),
primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -100),
primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9),
primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
]
updateConstraintsForOrientation()
}
func setupSudachiScreen() {
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
primaryScreen.clipsToBounds = true
primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor
primaryScreen.layer.borderWidth = 3
primaryScreen.layer.cornerCurve = .continuous
primaryScreen.layer.cornerRadius = 10
addSubview(primaryScreen)
portraitconstraints = [
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10),
primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10),
primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16),
]
landscapeconstraints = [
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10),
primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9),
primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
]
updateConstraintsForOrientation()
}
override func layoutSubviews() {
super.layoutSubviews()
updateConstraintsForOrientation()
}
private func updateConstraintsForOrientation() {
if userDefaults.bool(forKey: "isfullscreen") {
removeConstraints(portraitconstraints)
removeConstraints(landscapeconstraints)
removeConstraints(fullscreenconstraints)
addConstraints(fullscreenconstraints)
} else {
removeConstraints(portraitconstraints)
removeConstraints(landscapeconstraints)
let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
addConstraints(isPortrait ? portraitconstraints : landscapeconstraints)
}
}
}

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -10,13 +10,13 @@
<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">MIT license</a>. <br
</p> </p>
## Compatibility ## Compatibility
As of October 2024, MeloNX can only play the audio of games. MeloNX works on iPhone X and later and iPad 7th Gen and later. A lot of games work.
## Usage ## Usage
To run MeloNX on your iOS device, at least 8GB of RAM is recommended to ensure stability. For full instructions, refer to our [Setup Guide](https://github.com/MeloNX-Emu/MeloNX/wiki/Setup-Guide). 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).

18
compile.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# Define the destination directory (hardcoded)
DESTINATION_DIR="src/MeloNX/Dependencies/Dynamic\ Libraries/Ryujinx.Headless.SDL2.dylib"
# Restore the project
dotnet restore
# Build the project with the specified version
dotnet build -c Release
# Publish the project with the specified runtime and settings
dotnet publish -c Release -r ios-arm64 -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
# Move the published .dylib to the specified location
mv 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

View File

@ -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 - 2;
private const int CodeAlignment = 4; // Bytes. private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024; private const int CacheSize = 2047 * 1024 * 1024;
private const int CacheSizeIOS = 512 * 1024 * 1024; private const int CacheSizeIOS = 64 * 1024 * 1024;
private static ReservedRegion _jitRegion; private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator; private static JitCacheInvalidation _jitCacheInvalidator;
@ -189,7 +189,9 @@ namespace ARMeilleure.Translation.Cache
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment); int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}"); //DEBUG: Show JIT Memory Allocation
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
if (allocOffset < 0) if (allocOffset < 0)
{ {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
{
"originHash" : "1b46f7a56d6f994a826e31441c25b929398800cf38b3e9be23ae6e0ef8fc32c7",
"pins" : [
{
"identity" : "swiftsvg",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mchoe/SwiftSVG",
"state" : {
"branch" : "master",
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
}
},
{
"identity" : "swiftuijoystick",
"kind" : "remoteSourceControl",
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
"state" : {
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
"version" : "1.5.0"
}
}
],
"version" : 3
}

View File

@ -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>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1600" LastUpgradeVersion = "1610"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -15,7 +15,7 @@
buildForAnalyzing = "YES"> buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "4E87E28B2CCE2C1000F54325" BlueprintIdentifier = "4E80A98C2CD6F54500029585"
BuildableName = "MeloNX.app" BuildableName = "MeloNX.app"
BlueprintName = "MeloNX" BlueprintName = "MeloNX"
ReferencedContainer = "container:MeloNX.xcodeproj"> ReferencedContainer = "container:MeloNX.xcodeproj">
@ -29,9 +29,33 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES"> shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4E80A99C2CD6F54700029585"
BuildableName = "MeloNXTests.xctest"
BlueprintName = "MeloNXTests"
ReferencedContainer = "container:MeloNX.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4E80A9A62CD6F54700029585"
BuildableName = "MeloNXUITests.xctest"
BlueprintName = "MeloNXUITests"
ReferencedContainer = "container:MeloNX.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
@ -39,12 +63,14 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUValidationMode = "1"
showGraphicsOverview = "Yes"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "4E87E28B2CCE2C1000F54325" BlueprintIdentifier = "4E80A98C2CD6F54500029585"
BuildableName = "MeloNX.app" BuildableName = "MeloNX.app"
BlueprintName = "MeloNX" BlueprintName = "MeloNX"
ReferencedContainer = "container:MeloNX.xcodeproj"> ReferencedContainer = "container:MeloNX.xcodeproj">
@ -61,7 +87,7 @@
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "4E87E28B2CCE2C1000F54325" BlueprintIdentifier = "4E80A98C2CD6F54500029585"
BuildableName = "MeloNX.app" BuildableName = "MeloNX.app"
BlueprintName = "MeloNX" BlueprintName = "MeloNX"
ReferencedContainer = "container:MeloNX.xcodeproj"> ReferencedContainer = "container:MeloNX.xcodeproj">

View File

@ -9,13 +9,15 @@
<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> </dict>
<key>SuppressBuildableAutocreation</key> <key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>4E87E28B2CCE2C1000F54325</key> <key>orderHint</key>
<dict> <integer>4</integer>
<key>primary</key>
<true/>
</dict> </dict>
</dict> </dict>
</dict> </dict>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "271EB822-2830-4016-A3D7-CA2DEBEDCD27"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "499F5405-B63B-4623-9332-1E44FC449FD0"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/Views/GamesList/GameListView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "309"
endingLineNumber = "309"
landmarkName = "loadGames()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "0BB7C122-8933-48E8-ABA3-1ABB39594258"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/Models/Game.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "createImage(from:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -0,0 +1,42 @@
<?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>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>4E80A98C2CD6F54500029585</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>4E80A99C2CD6F54700029585</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>4E80A9A62CD6F54700029585</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View File

@ -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>

View File

@ -0,0 +1,49 @@
//
// Ryujinx-Header.h
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
#ifndef RyujinxHeader
#define RyujinxHeader
#import "SDL2/SDL.h"
#ifdef __cplusplus
extern "C" {
#endif
struct GameInfo {
long FileSize;
char TitleName[512];
long TitleId;
char Developer[256];
int Version;
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();
const char* get_game_controllers();
#ifdef __cplusplus
}
#endif
#endif /* RyujinxSDL_h */

View 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
}

View 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);

View File

@ -0,0 +1,91 @@
#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 (isJITEnabled()) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"JIT"];
[defaults synchronize]; // Ensure the value is saved immediately
} else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:NO forKey:@"JIT"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
NSLog(@"Entitlement Does Exist");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"increased-memory-limit"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.increased-debugging-memory-limit")) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"increased-debugging-memory-limit"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"extended-virtual-addressing"];
[defaults synchronize]; // Ensure the value is saved immediately
}
}

View File

@ -0,0 +1,189 @@
//
// 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() {
// Initialize SDL if not already initialized
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
}
// Create virtual controller
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
}

View File

@ -0,0 +1,66 @@
//
// VirtualController.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
import SwiftUI
func waitforcontroller() {
if let window = theWindow {
// Function to recursively search for GCControllerView
func findGCControllerView(in view: UIView) -> UIView? {
// Check if current view is GCControllerView
if String(describing: type(of: view)) == "ControllerView" {
return view
}
// Search through subviews
for subview in view.subviews {
if let found = findGCControllerView(in: subview) {
return found
}
}
return nil
}
let controllerView = ControllerView()
let controllerHostingController = UIHostingController(rootView: controllerView)
let containerView = TransparentHostingContainerView(frame: window.bounds)
containerView.backgroundColor = .clear
controllerHostingController.view.frame = containerView.bounds
controllerHostingController.view.backgroundColor = .clear
containerView.addSubview(controllerHostingController.view)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if findGCControllerView(in: window) == nil {
window.addSubview(containerView)
} else {
timer.invalidate()
}
window.bringSubviewToFront(containerView)
}
}
}
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)
// Return nil if the touch is outside visible content (passes through to views below)
return view === self ? nil : view
}
}

View File

@ -0,0 +1,54 @@
//
// 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 UserDefaults.standard.bool(forKey: "isVirtualController") {
if let window = theWindow {
class LandscapeViewController: UIViewController {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeLeft
}
}
let landscapeVC = LandscapeViewController()
landscapeVC.modalPresentationStyle = .fullScreen
theWindow?.rootViewController?.present(landscapeVC, animated: false, completion: nil)
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)
}
}

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}
}

View File

@ -0,0 +1,60 @@
//
// MTLHUD.swift
// MeloNX
//
// Created by Stossy11 on 26/11/2024.
//
import Foundation
class MTLHud {
var canMetalHud: Bool {
return openMetalDylib()
}
var isEnabled: Bool {
if let getenv = getenv("MTL_HUD_ENABLED") {
return String(cString: getenv).contains("1")
}
return false
}
static let shared = MTLHud()
private init() {
openMetalDylib()
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
enable()
} else {
disable()
}
}
func openMetalDylib() -> Bool {
let path = "/usr/lib/libMTLHud.dylib"
// Load the dynamic library
if dlopen(path, RTLD_NOW) != nil {
// Library loaded successfully
print("Library loaded from \(path)")
return true
} else {
// Handle error
if let error = String(validatingUTF8: dlerror()) {
print("Error loading library: \(error)")
}
return false
}
}
func enable() {
setenv("MTL_HUD_ENABLED", "1", 1)
}
func disable() {
setenv("MTL_HUD_ENABLED", "0", 1)
}
}

View File

@ -0,0 +1,316 @@
//
// 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)
}
}
}
class Ryujinx {
private var isRunning = false
let virtualController = VirtualController()
@Published var controllerMap: [Controller] = []
@State var firmwareversion = "0"
static let shared = Ryujinx()
private init() {}
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 fullscreen: Bool
var memoryManagerMode: String
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",
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.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
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 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
}
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
if config.fullscreen {
args.append(contentsOf: ["--aspect-ratio", "Stretched"])
}
if config.nintendoinput {
args.append("--correct-controller")
}
args.append("--disable-vsync")
if config.resscale != 1.0 {
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
}
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
}
}
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
}
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)")
}
}
static func log(_ message: String) {
print("[Ryujinx] \(message)")
}
}

View File

@ -0,0 +1,15 @@
//
// RyujinxError.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import Foundation
enum RyujinxError: Error {
case libraryLoadError
case executionError(code: Int32)
case alreadyRunning
case notRunning
}

View File

@ -0,0 +1,309 @@
//
// ContentView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import SwiftUI
// import SDL2
import GameController
import Darwin
import UIKit
import MetalKit
// import SDL
import SoftwareKeyboard
struct MoltenVKSettings: Codable, Hashable {
let string: String
var value: String
}
struct ContentView: View {
// MARK: - Properties
@State private var theWindow: UIWindow?
@State private var game: Game?
@State private var controllersList: [Controller] = []
@State private var currentControllers: [Controller] = []
@State private var config: Ryujinx.Configuration
@State var settings: [MoltenVKSettings]
@AppStorage("useTrollStore") var useTrollStore: Bool = false
@State private var isVirtualControllerActive: Bool = false
@AppStorage("isVirtualController") var isVCA: Bool = true
@State var onscreencontroller: Controller = Controller(id: "", name: "")
@AppStorage("JIT") var isJITEnabled: Bool = false
@State var isMK8: Bool = false
@AppStorage("quit") var quit: Bool = false
@State var quits: Bool = false
@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_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1")
]
_settings = State(initialValue: defaultSettings)
print("JIT Enabled: \(isJITEnabled)")
initializeSDL()
}
// MARK: - Body
var body: some View {
if let game, quits == false {
if isLoading {
emulationView
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
timer.invalidate()
quits = quit
if quits {
quit = false
timer.invalidate()
}
}
}
} else {
VStack {
}
.onAppear() {
isAnimating = false
}
}
} else {
mainMenuView
.onAppear() {
quits = false
}
}
}
// 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 {
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() {
refreshControllersList()
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
if !isJIT, useTrollStore {
askForJIT()
}
}
}
// MARK: - Helper Methods
var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
private func initializeSDL() {
setMoltenVKSettings()
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(SdlInitFlags)
initialize()
}
private func setupEmulation() {
patchMakeKeyAndVisible()
if (currentControllers.first(where: { $0 == onscreencontroller }) != nil) {
isVCA = true
DispatchQueue.main.async {
start(displayid: 1)
}
} else {
isVCA = false
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"})
if controllersList.count > 2 {
let controller = controllersList[2]
currentControllers.append(controller)
} else if let controller = controllersList.first(where: { $0.id == onscreencontroller.id }), !controllersList.isEmpty {
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)))
var setting: MoltenVKSettings
if game.titleName.lowercased() != "super mario odyssey" {
setting = (MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"))
} else {
setting = (MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"))
}
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)")
}
}
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
}
}

View File

@ -0,0 +1,304 @@
//
// 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()
}
}
.padding()
VStack {
ShoulderButtonsViewRight()
ZStack {
Joystick(iscool: true) // hope this works
ABXYView()
}
}
.padding()
}
HStack {
ButtonView(button: .start) // Adding the + button
.padding(.horizontal, 40)
ButtonView(button: .back) // Adding the - button
.padding(.horizontal, 40)
}
}
.padding(.bottom, geometry.size.height / 3.2) // very broken
}
} 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 ""
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -0,0 +1,493 @@
//
// GameListView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import SwiftUI
import UniformTypeIdentifiers
struct GameLibraryView: View {
@Binding var startemu: Game?
@State private var games: [Game] = []
@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
var filteredGames: [Game] {
if searchText.isEmpty {
return games
}
return 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 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)
.onTapGesture {
addToRecentGames(game)
}
}
}
}
} else {
LazyVStack(spacing: 2) {
ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu)
.onTapGesture {
addToRecentGames(game)
}
}
}
}
}
}
.onAppear {
loadGames()
loadRecentGames()
let firmware = Ryujinx.shared.fetchFirmwareVersion()
firmwareversion = (firmware == "" ? "0" : firmware)
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Menu {
Text("Firmware Version: \(firmwareversion)")
.tint(.white)
if firmwareversion == "0" {
Button {
firmwareInstaller.toggle()
} label: {
Text("Install Firmware")
}
} else {
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 {
isImporting.toggle()
} label: {
Text("Open game from system")
}
}
Button {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
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: $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)
}
}
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .data]) { 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)
var game = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
game.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.titleId = String(gameInfo.TitleId)
print(String(gameInfo.TitleId))
game.version = String(gameInfo.Version)
game.icon = game.createImage(from: gameInfo)
DispatchQueue.main.async {
startemu = game
}
} catch {
print(error)
}
case .failure(let err):
print("File import failed: \(err.localizedDescription)")
}
}
}
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 = []
}
}
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)")
}
}
games = []
// Load games only from "roms" folder
do {
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
files.forEach { fileURLCandidate in
do {
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
var gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
var game = Game(containerFolder: romsDirectory, fileType: .item, fileURL: fileURLCandidate, titleName: "", titleId: "", developer: "", version: "")
game.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.titleId = String(gameInfo.TitleId)
game.version = String(gameInfo.Version)
game.icon = game.createImage(from: gameInfo)
games.append(game)
} catch {
print(error)
}
}
} catch {
print("Error loading games from roms folder: \(error)")
}
}
}
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)
}
}
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)
}
}
struct GameListRow: View {
let game: Game
@Binding var startemu: Game?
@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 {
Button {
startemu = game
} label: {
Label("Play Now", systemImage: "play.fill")
}
Button {
// Add info action
} label: {
Label("Game Info", systemImage: "info.circle")
}
}
}
.buttonStyle(.plain)
}
}

View File

@ -0,0 +1,430 @@
//
// 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("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("performacehud") var performacehud: Bool = false
@State private var showResolutionInfo = 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 {
Toggle(isOn: $config.fullscreen) {
labelWithIcon("Fullscreen", iconName: "rectangle.expand.vertical")
}
.tint(.blue)
Toggle(isOn: $config.disableShaderCache) {
labelWithIcon("Shader Cache", iconName: "memorychip")
}
.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)
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.1) {
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)
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(controllersList) { 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)
} label: {
let controller = String((controllersList.firstIndex(where: { $0.id == controller.id }) ?? 0) + 1)
Text("Player \(controller)")
}
}
}
} 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")
}
}
} header: {
Text("CPU Mode")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Select how memory is managed. 'Host (fast)' is best for most users.")
}
// Other Settings
Section {
Toggle(isOn: $useTrollStore) {
labelWithIcon("TrollStore", iconName: "troll.svg")
}
.tint(.blue)
Toggle(isOn: $config.debuglogs) {
labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble")
}
.tint(.blue)
Toggle(isOn: $config.tracelogs) {
labelWithIcon("Trace Logs", iconName: "waveform.path")
}
.tint(.blue)
} header: {
Text("Miscellaneous Options")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Enable logs for troubleshooting and Enable automatic TrollStore JIT.")
}
// Advanced
Section {
DisclosureGroup {
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: {
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)")
}
}
.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
}
// 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
}
}
}

View 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")
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,18 @@
//
// SoftwareKeyboard.h
// SoftwareKeyboard
//
// Created by Stossy11 on 19/12/2024.
//
#import <Foundation/Foundation.h>
//! Project version number for SoftwareKeyboard.
FOUNDATION_EXPORT double SoftwareKeyboardVersionNumber;
//! Project version string for SoftwareKeyboard.
FOUNDATION_EXPORT const unsigned char SoftwareKeyboardVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SoftwareKeyboard/PublicHeader.h>

View File

@ -0,0 +1,38 @@
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 6.0.3 effective-5.10 (swiftlang-6.0.3.1.4 clang-1600.0.30)
// swift-module-flags: -target arm64-apple-ios14.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone -enable-experimental-feature OpaqueTypeErasure -enable-bare-slash-regex -module-name SoftwareKeyboard
@_exported import SoftwareKeyboard
import Swift
import UIKit
import _Concurrency
import _StringProcessing
import _SwiftConcurrencyShims
@objc public enum KeyboardMode : Swift.UInt32 {
case `default` = 0
case numeric = 1
case ascii = 2
case fullLatin = 3
case alphabet = 4
case simplifiedChinese = 5
case traditionalChinese = 6
case korean = 7
case languageSet2 = 8
case languageSet2Latin = 9
public init?(rawValue: Swift.UInt32)
public typealias RawValue = Swift.UInt32
public var rawValue: Swift.UInt32 {
get
}
}
public struct SoftwareKeyboardUiArgs {
public var keyboardMode: SoftwareKeyboard.KeyboardMode
public var headerText: Swift.String
public var subtitleText: Swift.String
public var submitText: Swift.String
public var stringLengthMin: Swift.Int32
public var stringLengthMax: Swift.Int32
public var initialText: Swift.String?
}
extension SoftwareKeyboard.KeyboardMode : Swift.Equatable {}
extension SoftwareKeyboard.KeyboardMode : Swift.Hashable {}
extension SoftwareKeyboard.KeyboardMode : Swift.RawRepresentable {}

View File

@ -0,0 +1,38 @@
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 6.0.3 effective-5.10 (swiftlang-6.0.3.1.4 clang-1600.0.30)
// swift-module-flags: -target arm64-apple-ios14.0 -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone -enable-experimental-feature OpaqueTypeErasure -enable-bare-slash-regex -module-name SoftwareKeyboard
@_exported import SoftwareKeyboard
import Swift
import UIKit
import _Concurrency
import _StringProcessing
import _SwiftConcurrencyShims
@objc public enum KeyboardMode : Swift.UInt32 {
case `default` = 0
case numeric = 1
case ascii = 2
case fullLatin = 3
case alphabet = 4
case simplifiedChinese = 5
case traditionalChinese = 6
case korean = 7
case languageSet2 = 8
case languageSet2Latin = 9
public init?(rawValue: Swift.UInt32)
public typealias RawValue = Swift.UInt32
public var rawValue: Swift.UInt32 {
get
}
}
public struct SoftwareKeyboardUiArgs {
public var keyboardMode: SoftwareKeyboard.KeyboardMode
public var headerText: Swift.String
public var subtitleText: Swift.String
public var submitText: Swift.String
public var stringLengthMin: Swift.Int32
public var stringLengthMax: Swift.Int32
public var initialText: Swift.String?
}
extension SoftwareKeyboard.KeyboardMode : Swift.Equatable {}
extension SoftwareKeyboard.KeyboardMode : Swift.Hashable {}
extension SoftwareKeyboard.KeyboardMode : Swift.RawRepresentable {}

View File

@ -0,0 +1,6 @@
framework module SoftwareKeyboard {
umbrella header "SoftwareKeyboard.h"
export *
module * { export * }
}

Some files were not shown because too many files have changed in this diff Show More