1
0
forked from MeloNX/MeloNX

Compare commits

...

72 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
83 changed files with 5866 additions and 1037 deletions

2
.gitignore vendored
View File

@ -10,6 +10,8 @@
# Build results
dotnet.xcconfig
[Dd]ebug/
[Rr]elease/
x64/

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

@ -24,13 +24,13 @@ namespace ARMeilleure.Native
public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
// When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
//pthread_jit_write_protect_np(0);
// pthread_jit_write_protect_np(0);
var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
srcSpan.CopyTo(dstSpan);
//pthread_jit_write_protect_np(1);
// pthread_jit_write_protect_np(1);
// Ensure that the instruction cache for this range is invalidated.
sys_icache_invalidate(dst, (IntPtr)n);

View File

@ -15,11 +15,11 @@ namespace ARMeilleure.Translation.Cache
static partial class JitCache
{
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 CacheSize = 2047 * 1024 * 1024;
private const int CacheSizeIOS = 512 * 1024 * 1024;
private const int CacheSizeIOS = 64 * 1024 * 1024;
private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;

View File

@ -3,12 +3,31 @@
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objectVersion = 73;
objects = {
/* Begin PBXAggregateTarget section */
BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */ = {
isa = PBXAggregateTarget;
buildConfigurationList = BD43C6222D1B248D003BBC42 /* Build configuration list for PBXAggregateTarget "com.Stossy11.MeloNX.RyujinxAg" */;
buildPhases = (
BD43C62A2D1B252F003BBC42 /* ShellScript */,
);
dependencies = (
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */,
);
name = com.Stossy11.MeloNX.RyujinxAg;
packageProductDependencies = (
);
productName = com.Stossy11.MeloNX.RyujinxAg;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4E80AA212CD705DD00029585 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E80AA202CD705DD00029585 /* SDL */; };
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
4E4854022D138D7600A446A6 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
5650564B2D2A758600C8BB1E /* dotnet.xcconfig.example in Resources */ = {isa = PBXBuildFile; fileRef = 5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -26,6 +45,13 @@
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
remoteInfo = MeloNX;
};
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
proxyType = 1;
remoteGlobalIDString = BD43C61D2D1B23AB003BBC42;
remoteInfo = Ryujinx;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -46,10 +72,12 @@
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = dotnet.xcconfig.example; sourceTree = "<group>"; };
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */ = {
565056492D2A756A00C8BB1E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@ -59,33 +87,74 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = {
5650564D2D2A75B300C8BB1E /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = {
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (CodeSignOnCopy, );
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (CodeSignOnCopy, );
"Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, );
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libteakra.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (
CodeSignOnCopy,
);
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (
CodeSignOnCopy,
);
"Dependencies/Dynamic Libraries/libavutil.dylib" = (
CodeSignOnCopy,
);
Dependencies/XCFrameworks/MoltenVK.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/SDL2.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libSPIRV.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libavcodec.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libavfilter.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libavformat.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libavutil.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libswresample.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libswscale.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
Dependencies/XCFrameworks/libteakra.xcframework = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
};
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
membershipExceptions = (
"Dependencies/Dynamic Libraries/libavcodec.dylib",
"Dependencies/Dynamic Libraries/libavutil.dylib",
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework",
Dependencies/XCFrameworks/libavcodec.xcframework,
Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework,
Dependencies/XCFrameworks/libavutil.xcframework,
Dependencies/XCFrameworks/libSPIRV.xcframework,
Dependencies/XCFrameworks/libswresample.xcframework,
Dependencies/XCFrameworks/libswscale.xcframework,
Dependencies/XCFrameworks/libteakra.xcframework,
@ -96,25 +165,9 @@
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4E80A98F2CD6F54500029585 /* MeloNX */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */,
);
path = MeloNX;
sourceTree = "<group>";
};
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = MeloNXTests;
sourceTree = "<group>";
};
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = MeloNXUITests;
sourceTree = "<group>";
};
4E80A98F2CD6F54500029585 /* MeloNX */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (565056492D2A756A00C8BB1E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 5650564D2D2A75B300C8BB1E /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MeloNX; sourceTree = "<group>"; };
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXTests; sourceTree = "<group>"; };
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXUITests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@ -122,8 +175,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */,
4E80AA212CD705DD00029585 /* SDL in Frameworks */,
4E4854022D138D7600A446A6 /* GameController.framework in Frameworks */,
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -147,6 +201,8 @@
4E80A9842CD6F54500029585 = {
isa = PBXGroup;
children = (
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */,
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
4E80A98F2CD6F54500029585 /* MeloNX */,
4E80A9A02CD6F54700029585 /* MeloNXTests */,
4E80A9AA2CD6F54700029585 /* MeloNXUITests */,
@ -175,6 +231,25 @@
};
/* End PBXGroup section */
/* Begin PBXLegacyTarget section */
BD43C61D2D1B23AB003BBC42 /* Ryujinx */ = {
isa = PBXLegacyTarget;
buildArgumentsString = "publish -c Release -r ios-arm64 -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true";
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
buildPhases = (
);
buildToolPath = "$(DOTNET_PATH)";
buildWorkingDirectory = "$(SRCROOT)/../..";
dependencies = (
);
name = Ryujinx;
packageProductDependencies = (
);
passBuildSettingsInEnvironment = 0;
productName = Ryujinx;
};
/* End PBXLegacyTarget section */
/* Begin PBXNativeTarget section */
4E80A98C2CD6F54500029585 /* MeloNX */ = {
isa = PBXNativeTarget;
@ -194,7 +269,8 @@
);
name = MeloNX;
packageProductDependencies = (
4E80AA202CD705DD00029585 /* SDL */,
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */,
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
);
productName = MeloNX;
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
@ -267,6 +343,12 @@
CreatedOnToolsVersion = 16.1;
TestTargetID = 4E80A98C2CD6F54500029585;
};
BD43C61D2D1B23AB003BBC42 = {
CreatedOnToolsVersion = 16.2;
};
BD43C6212D1B248D003BBC42 = {
CreatedOnToolsVersion = 16.2;
};
};
};
buildConfigurationList = 4E80A9882CD6F54500029585 /* Build configuration list for PBXProject "MeloNX" */;
@ -279,9 +361,10 @@
mainGroup = 4E80A9842CD6F54500029585;
minimizedProjectReferenceProxies = 1;
packageReferences = (
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */,
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */,
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
);
preferredProjectObjectVersion = 77;
preferredProjectObjectVersion = 56;
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -289,6 +372,8 @@
4E80A98C2CD6F54500029585 /* MeloNX */,
4E80A99C2CD6F54700029585 /* MeloNXTests */,
4E80A9A62CD6F54700029585 /* MeloNXUITests */,
BD43C61D2D1B23AB003BBC42 /* Ryujinx */,
BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */,
);
};
/* End PBXProject section */
@ -298,6 +383,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5650564B2D2A758600C8BB1E /* dotnet.xcconfig.example in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -317,6 +403,28 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
BD43C62A2D1B252F003BBC42 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(SRCROOT)/../../src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib",
);
outputFileListPaths = (
);
outputPaths = (
"$(SRCROOT)/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
4E80A9892CD6F54500029585 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@ -352,6 +460,11 @@
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
};
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
targetProxy = BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@ -360,6 +473,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@ -390,17 +504,20 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
EAGER_LINKING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_FAST_MATH = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_UNROLL_LOOPS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@ -408,12 +525,14 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LLVM_LTO = YES_THIN;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
@ -423,6 +542,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@ -453,11 +573,15 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
EAGER_LINKING = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_FAST_MATH = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = fast;
GCC_UNROLL_LOOPS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@ -465,11 +589,15 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LLVM_LTO = YES_THIN;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_DISABLE_SAFETY_CHECKS = YES;
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -485,16 +613,29 @@
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES;
ENABLE_TESTABILITY = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
INFOPLIST_KEY_GCSupportsGameMode = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
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_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -554,12 +695,134 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(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/XCFrameworks",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/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/Core/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;
MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -576,17 +839,29 @@
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
ENABLE_PREVIEWS = YES;
GCC_OPTIMIZATION_LEVEL = 3;
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
INFOPLIST_KEY_GCSupportsGameMode = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
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_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -646,12 +921,134 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(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/XCFrameworks",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/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/Core/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;
MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -729,6 +1126,52 @@
};
name = Release;
};
BD43C61F2D1B23AB003BBC42 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 95J8WZ4TN8;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
BD43C6202D1B23AB003BBC42 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
BD43C6232D1B248D003BBC42 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 95J8WZ4TN8;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
BD43C6242D1B248D003BBC42 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 95J8WZ4TN8;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -768,24 +1211,55 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BD43C61F2D1B23AB003BBC42 /* Debug */,
BD43C6202D1B23AB003BBC42 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BD43C6222D1B248D003BBC42 /* Build configuration list for PBXAggregateTarget "com.Stossy11.MeloNX.RyujinxAg" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BD43C6232D1B248D003BBC42 /* Debug */,
BD43C6242D1B248D003BBC42 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = {
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ctreffs/SwiftSDL2";
repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.4.1;
minimumVersion = 1.5.0;
};
};
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mchoe/SwiftSVG";
requirement = {
branch = master;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
4E80AA202CD705DD00029585 /* SDL */ = {
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = {
isa = XCSwiftPackageProductDependency;
package = 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */;
productName = SDL;
package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */;
productName = SwiftUIJoystick;
};
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
isa = XCSwiftPackageProductDependency;
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
productName = SwiftSVG;
};
/* End XCSwiftPackageProductDependency section */
};

View File

@ -1,13 +1,22 @@
{
"originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5",
"originHash" : "1b46f7a56d6f994a826e31441c25b929398800cf38b3e9be23ae6e0ef8fc32c7",
"pins" : [
{
"identity" : "swiftsdl2",
"identity" : "swiftsvg",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ctreffs/SwiftSDL2",
"location" : "https://github.com/mchoe/SwiftSVG",
"state" : {
"revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a",
"version" : "1.4.1"
"branch" : "master",
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
}
},
{
"identity" : "swiftuijoystick",
"kind" : "remoteSourceControl",
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
"state" : {
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
"version" : "1.5.0"
}
}
],

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

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>MeloNX.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
</dict>
</dict>
</dict>
</plist>

View File

@ -7,32 +7,33 @@
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1F3099D0-0456-4AD5-8EA1-52BABAF2AA89"
shouldBeEnabled = "Yes"
nameForDebugger = "Ignore-SIGUSR"
uuid = "499F5405-B63B-4623-9332-1E44FC449FD0"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "Yes"
filePath = "MeloNX/MeloNXApp.swift"
continueAfterRunningActions = "No"
filePath = "MeloNX/Views/GamesList/GameListView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "14"
endingLineNumber = "14"
landmarkName = "body"
landmarkType = "24">
<Actions>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "process handle SIGUSR1 -s false -n false">
</ActionContent>
</BreakpointActionProxy>
<BreakpointActionProxy
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
<ActionContent
consoleCommand = "process handle SIGBUS -s false -n false">
</ActionContent>
</BreakpointActionProxy>
</Actions>
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>

View File

@ -9,6 +9,16 @@
<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>

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

@ -7,6 +7,7 @@
import Foundation
class MTLHud {
var canMetalHud: Bool {

View File

@ -7,12 +7,11 @@
import Foundation
import SwiftUI
import SDL2
import GameController
struct Controller: Identifiable, Hashable {
let id: String
let name: String
var id: String
var name: String
}
struct iOSNav<Content: View>: View {
@ -32,13 +31,16 @@ struct iOSNav<Content: View>: View {
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 {
public struct Configuration : Codable, Equatable {
var gamepath: String
var inputids: [String]
var resscale: Float
@ -49,7 +51,6 @@ class Ryujinx {
var listinputids: Bool
var fullscreen: Bool
var memoryManagerMode: String
var disableVSync: Bool
var disableShaderCache: Bool
var disableDockedMode: Bool
var enableTextureRecompression: Bool
@ -63,7 +64,6 @@ class Ryujinx {
listinputids: Bool = false,
fullscreen: Bool = true,
memoryManagerMode: String = "HostMapped",
disableVSync: Bool = false,
disableShaderCache: Bool = false,
disableDockedMode: Bool = false,
nintendoinput: Bool = true,
@ -78,7 +78,6 @@ class Ryujinx {
self.tracelogs = tracelogs
self.listinputids = listinputids
self.fullscreen = fullscreen
self.disableVSync = disableVSync
self.disableShaderCache = disableShaderCache
self.disableDockedMode = disableDockedMode
self.enableTextureRecompression = enableTextureRecompression
@ -98,10 +97,12 @@ class Ryujinx {
isRunning = true
// Start The Emulation on the main thread
DispatchQueue.main.async {
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) }
@ -113,6 +114,10 @@ class Ryujinx {
if result != 0 {
self.isRunning = false
if accessing {
url.stopAccessingSecurityScopedResource()
}
throw RyujinxError.executionError(code: result)
}
} catch {
@ -145,33 +150,35 @@ class Ryujinx {
args.append("--graphics-backend")
args.append("Vulkan")
// Fixes the Stubs.DispatchLoop Crash
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: ["--exclusive-fullscreen", String(config.fullscreen)])
args.append(contentsOf: ["--exclusive-fullscreen-width", "1280"])
args.append(contentsOf: ["--exclusive-fullscreen-height", "720"])
args.append(contentsOf: ["--aspect-ratio", "Stretched"])
}
if config.resscale != 1 {
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.nintendoinput {
args.append("--correct-ons-controller")
}
if config.enableInternet {
args.append("--enable-internet-connection")
}
// Adding default args directly into additionalArgs
if config.disableVSync {
args.append("--disable-vsync")
}
if config.disableShaderCache {
if !config.disableShaderCache { // same with disableShaderCache
args.append("--disable-shader-cache")
}
if config.disableDockedMode {
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 {
@ -203,8 +210,41 @@ class Ryujinx {
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 []
}
@ -232,6 +272,38 @@ class Ryujinx {
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)")
}
}

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" : [
{
"filename" : "nxgradientpng.png",
"idiom" : "universal",
"platform" : "ios",
"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

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

View File

@ -1,99 +0,0 @@
//
// VirtualController.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
public var controllerCallback: (() -> Void)?
var VirtualController: GCVirtualController!
func showVirtualController() {
let config = GCVirtualController.Configuration()
if UserDefaults.standard.bool(forKey: "RyuDemoControls") {
config.elements = [
GCInputLeftThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX,
GCInputButtonY,
// GCInputRightThumbstick,
GCInputRightTrigger,
GCInputLeftTrigger,
GCInputLeftShoulder,
GCInputRightShoulder
]
} else {
config.elements = [
GCInputLeftThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX,
GCInputButtonY,
GCInputRightThumbstick,
GCInputRightTrigger,
GCInputLeftTrigger,
GCInputLeftShoulder,
GCInputRightShoulder
]
}
VirtualController = GCVirtualController(configuration: config)
VirtualController.connect { err in
print("controller connect: \(String(describing: err))")
patchMakeKeyAndVisible()
if let controllerCallback {
controllerCallback()
}
}
}
func waitforcontroller() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
if let window = UIApplication.shared.windows.first {
// Function to recursively search for GCControllerView
func findGCControllerView(in view: UIView) -> UIView? {
// Check if current view is GCControllerView
if String(describing: type(of: view)) == "GCControllerView" {
return view
}
// Search through subviews
for subview in view.subviews {
if let found = findGCControllerView(in: subview) {
return found
}
}
return nil
}
if let gcControllerView = findGCControllerView(in: window) {
// Found the GCControllerView
print("Found GCControllerView:", gcControllerView)
if let theWindow = theWindow, (findGCControllerView(in: theWindow) == nil) {
theWindow.addSubview(gcControllerView)
theWindow.bringSubviewToFront(gcControllerView)
}
}
}
}
}
@available(iOS 15.0, *)
func reconnectVirtualController() {
VirtualController.disconnect()
DispatchQueue.main.async {
VirtualController.connect { err in
print("reconnected: err \(String(describing: err))")
}
}
}

View File

@ -1,40 +0,0 @@
//
// Untitled.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
var theWindow: UIWindow? = nil
extension UIWindow {
@objc func wdb_makeKeyAndVisible() {
if #available(iOS 13.0, *) {
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
}
self.wdb_makeKeyAndVisible()
theWindow = self
if #available(iOS 15.0, *) {
reconnectVirtualController()
}
if let window = theWindow {
waitforcontroller()
}
}
}
func patchMakeKeyAndVisible() {
let uiwindowClass = UIWindow.self
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
method_exchangeImplementations(m1, m2)
}
}

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

View File

@ -2,6 +2,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>MeloID</key>
<string></string>
<key>UIFileSharingEnabled</key>
<true/>
</dict>

View File

@ -4,8 +4,6 @@
<dict>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
</dict>

View File

@ -6,12 +6,118 @@
//
import SwiftUI
import UIKit
@main
struct MeloNXApp: App {
@AppStorage("showeddrmcheck") var showed = true
init() {
DispatchQueue.main.async { [self] in
// drmcheck()
if showed {
drmcheck() { bool in
if bool {
print("Yippee")
} else {
// exit(0)
}
}
} else {
showAlert()
}
}
}
var body: some Scene {
WindowGroup {
ContentView()
if showed {
ContentView()
} else {
HStack {
Text("Loading...")
ProgressView()
}
}
}
}
func showAlert() {
// Create the alert controller
if let mainWindow = UIApplication.shared.windows.last {
let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert)
// Add a text field to the alert
alertController.addTextField { textField in
textField.placeholder = "Enter key here"
}
// Add the "OK" action
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
// Get the text entered in the text field
if let textField = alertController.textFields?.first, let enteredText = textField.text {
print("Entered text: \(enteredText)")
UserDefaults.standard.set(enteredText, forKey: "MeloDRMID")
drmcheck() { bool in
if bool {
showed = true
} else {
exit(0)
}
}
}
}
alertController.addAction(okAction)
// Add a "Cancel" action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
// Present the alert
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
} else {
exit(0)
}
}
}
func drmcheck(completion: @escaping (Bool) -> Void) {
if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() {
if let value = UserDefaults.standard.string(forKey: "MeloDRMID") {
if let url = URL(string: "https://mx.stossy11.com/auth/\(value)/\(base64device)") {
print(url)
// Create a URLSession
let session = URLSession.shared
// Create a data task
let task = session.dataTask(with: url) { data, response, error in
// Handle errors
if let error = error {
exit(0)
}
// Check response and data
if let response = response as? HTTPURLResponse, response.statusCode == 200 {
print("Successfully Recieved API Data")
completion(true)
} else if let response = response as? HTTPURLResponse, response.statusCode == 201 {
print("Successfully Created Auth UUID")
completion(true)
} else {
completion(false)
}
}
// Start the task
task.resume()
}
} else {
completion(false)
}
} else {
completion(false)
}
}

View File

@ -0,0 +1,45 @@
//
// GameInfo.swift
// MeloNX
//
// Created by Stossy11 on 9/12/2024.
//
import SwiftUI
import UniformTypeIdentifiers
public struct Game: Identifiable, Equatable {
public var id = UUID()
var containerFolder: URL
var fileType: UTType
var fileURL: URL
var titleName: String
var titleId: String
var developer: String
var version: String
var icon: UIImage?
func createImage(from gameInfo: GameInfo) -> UIImage? {
// Access the struct
let gameInfoValue = gameInfo
// Get the image data
let imageSize = Int(gameInfoValue.ImageSize)
guard imageSize > 0, imageSize <= 1024 * 1024 else {
print("Invalid image size.")
return nil
}
// Convert the ImageData byte array to Swift's Data
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
// Create a UIImage (or NSImage on macOS)
print(imageData)
return UIImage(data: imageData)
}
}

View File

@ -1,245 +0,0 @@
//
// ContentView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import SwiftUI
import SDL2
import GameController
struct MoltenVKSettings: Codable, Hashable {
let string: String
var bool: Bool?
var value: String?
}
struct ContentView: View {
// MARK: - Properties
@State private var theWindow: UIWindow?
@State private var virtualController: GCVirtualController?
@State private var game: URL?
@State private var controllersList: [Controller] = []
@State private var currentControllers: [Controller] = []
@State private var config: Ryujinx.Configuration
@State private var settings: [MoltenVKSettings]
@State private var isVirtualControllerActive: Bool = false
// MARK: - Initialization
init() {
let defaultConfig = Ryujinx.Configuration(gamepath: "")
_config = State(initialValue: defaultConfig)
let defaultSettings: [MoltenVKSettings] = [
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1")
]
_settings = State(initialValue: defaultSettings)
initializeSDL()
}
// MARK: - Body
var body: some View {
iOSNav {
if let game {
emulationView
} else {
mainMenuView
}
}
.onChange(of: isVirtualControllerActive) { newValue in
if newValue {
createVirtualController()
} else {
destroyVirtualController()
}
}
}
// MARK: - View Components
private var emulationView: some View {
ZStack {}
.onAppear {
setupEmulation()
}
}
private var mainMenuView: some View {
HStack {
GameListView(startemu: $game)
.onAppear {
createVirtualController()
refreshControllersList()
}
settingsListView
}
}
private var settingsListView: some View {
List {
Section("Settings") {
NavigationLink("Config") {
SettingsView(config: $config, MoltenVKSettings: $settings)
}
}
Section("Controller") {
Button("Refresh", action: refreshControllersList)
ForEach(controllersList, id: \.self) { controller in
if controller.name != "Apple Touch Controller" {
controllerRow(for: controller)
}
}
}
}
}
private func controllerRow(for controller: Controller) -> some View {
HStack {
Button(controller.name) {
toggleController(controller)
}
Spacer()
if currentControllers.contains(where: { $0.id == controller.id }) {
Image(systemName: "checkmark.circle.fill")
}
}
}
// MARK: - Controller Management
private func createVirtualController() {
let configuration = GCVirtualController.Configuration()
configuration.elements = [
/*
GCInputLeftThumbstick,
GCInputRightThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX,
GCInputButtonY,
*/
]
virtualController = GCVirtualController(configuration: configuration)
virtualController?.connect()
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
}
private func destroyVirtualController() {
virtualController?.disconnect()
virtualController = nil
// Remove virtual controller from current controllers
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
}
// MARK: - Helper Methods
private func initializeSDL() {
DispatchQueue.main.async {
setMoltenVKSettings()
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(SDL_INIT_VIDEO)
}
}
private func setupEmulation() {
virtualController?.disconnect()
controllerCallback = {
DispatchQueue.main.async {
controllersList = Ryujinx.shared.getConnectedControllers()
currentControllers.removeAll(where: { $0.name == "Apple Touch Controller" })
if controllersList.count == 2,
controllersList.contains(where: { $0.name == "Apple Touch Controller" }) {
currentControllers.append(controllersList[1])
}
print(currentControllers)
start(displayid: 1)
}
}
showVirtualController()
}
private func refreshControllersList() {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
controllersList = Ryujinx.shared.getConnectedControllers()
controllersList.removeAll(where: { $0.id == "0" })
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
if let controller = controllersList.first, !controllersList.isEmpty {
currentControllers.append(controller)
}
}
}
private func toggleController(_ controller: Controller) {
if currentControllers.contains(where: { $0.id == controller.id }) {
currentControllers.removeAll(where: { $0.id == controller.id })
} else {
currentControllers.append(controller)
}
}
private func start(displayid: UInt32) {
guard let game else { return }
config.gamepath = game.path
config.inputids = currentControllers.map(\.id)
allocateMemory()
do {
try Ryujinx.shared.start(with: config)
} catch {
print("Error: \(error.localizedDescription)")
}
}
private func allocateMemory() {
let physicalMemory = ProcessInfo.processInfo.physicalMemory
let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024)
let pointer = UnsafeMutableRawPointer.allocate(
byteCount: Int(totalMemoryInGB),
alignment: MemoryLayout<UInt8>.alignment
)
pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(totalMemoryInGB))
}
private func setMoltenVKSettings() {
if let configs = loadSettings() {
self.config = configs
}
settings.forEach { setting in
setenv(setting.string, setting.value, 1)
}
}
}
// MARK: - Helper Functions
func loadSettings() -> Ryujinx.Configuration? {
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
let data = jsonString.data(using: .utf8) else {
return nil
}
do {
return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
} catch {
print("Failed to load settings: \(error)")
return nil
}
}

View File

@ -1,50 +0,0 @@
//
// GameListView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
// MARK: - This will most likely not be used in prod
import SwiftUI
struct GameListView: View {
@Binding var startemu: URL?
@State private var games: [URL] = []
var body: some View {
List(games, id: \.self) { game in
Button {
startemu = game
} label: {
Text(game.lastPathComponent)
}
}
.navigationTitle("Games")
.onAppear(perform: loadGames)
}
private func loadGames() {
let fileManager = FileManager.default
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
// Check if "roms" folder exists; if not, create it
if !fileManager.fileExists(atPath: romsDirectory.path) {
do {
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
} catch {
print("Failed to create roms directory: \(error)")
}
}
// Load games only from "roms" folder
do {
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
games = files
} catch {
print("Error loading games from roms folder: \(error)")
}
}
}

View File

@ -1,84 +0,0 @@
//
// VulkanSDLView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import UIKit
import MetalKit
import SDL2
class SDLView: UIView {
var sdlwin: OpaquePointer?
override init(frame: CGRect) {
super.init(frame: frame)
DispatchQueue.main.async { [self] in
makeSDLWindow()
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
DispatchQueue.main.async { [self] in
makeSDLWindow()
}
}
func getWindowFlags() -> UInt32 {
return SDL_WINDOW_VULKAN.rawValue
}
private func makeSDLWindow() {
let width: Int32 = 1280 // Replace with the desired width
let height: Int32 = 720 // Replace with the desired height
let defaultFlags: UInt32 = SDL_WINDOW_SHOWN.rawValue
let fullscreenFlag: UInt32 = SDL_WINDOW_FULLSCREEN.rawValue // Or SDL_WINDOW_FULLSCREEN_DESKTOP if needed
// Create the SDL window
sdlwin = SDL_CreateWindow(
"Ryujinx",
0,
0,
width,
height,
defaultFlags | getWindowFlags() // | fullscreenFlag | getWindowFlags()
)
// Check if we successfully retrieved the SDL window
guard sdlwin != nil else {
print("Error creating SDL window: \(String(cString: SDL_GetError()))")
return
}
print("SDL window created successfully.")
// Position SDL window over this UIView
self.syncSDLWindowPosition()
}
private func syncSDLWindowPosition() {
guard let sdlwin = sdlwin else { return }
// Get the frame of the UIView in screen coordinates
let viewFrameInWindow = self.convert(self.bounds, to: nil)
// Set the SDL window position and size to match the UIView frame
SDL_SetWindowPosition(sdlwin, Int32(viewFrameInWindow.origin.x), Int32(viewFrameInWindow.origin.y))
SDL_SetWindowSize(sdlwin, Int32(viewFrameInWindow.width), Int32(viewFrameInWindow.height))
// Bring SDL window to the front
SDL_RaiseWindow(sdlwin)
print("SDL window positioned over SDLView.")
}
override func layoutSubviews() {
super.layoutSubviews()
// Adjust SDL window whenever layout changes
syncSDLWindowPosition()
}
}

View File

@ -1,27 +0,0 @@
//
// VulkanSDLViewRepresentable.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import UIKit
import SwiftUI
import SDL2
import GameController
struct SDLViewRepresentable: UIViewRepresentable {
let configure: (UInt32) -> Void
func makeUIView(context: Context) -> SDLView {
// Configure (start ryu) before initialsing SDLView so SDLView can get the SDL_Window from Ryu
let view = SDLView(frame: .zero)
configure(SDL_GetWindowID(view.sdlwin))
return view
}
func updateUIView(_ uiView: SDLView, context: Context) {
}
}

View File

@ -1,183 +0,0 @@
//
// SettingsView.swift
// MeloNX
//
// Created by Stossy11 on 25/11/2024.
//
import SwiftUI
struct SettingsView: View {
@Binding var config: Ryujinx.Configuration
@Binding var MoltenVKSettings: [MoltenVKSettings]
var memoryManagerModes = [
("HostMapped", "Host (fast)"),
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
("SoftwarePageTable", "Software (slow)"),
]
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
var body: some View {
ScrollView {
VStack {
Section(header: Title("Graphics and Performance")) {
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
Toggle("Disable V-Sync", isOn: $config.disableVSync)
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
Resolution(value: $config.resscale)
Toggle("Enable Metal HUD", isOn: $metalHUDEnabled)
.onChange(of: metalHUDEnabled) { newValue in
if newValue {
MTLHud.shared.enable()
} else {
MTLHud.shared.disable()
}
}
}
Section(header: Title("Input Settings")) {
Toggle("List Input IDs", isOn: $config.listinputids)
Toggle("Nintendo Controller Layout", isOn: $config.nintendoinput)
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo)
// Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory)
}
Section(header: Title("Logging Settings")) {
Toggle("Enable Debug Logs", isOn: $config.debuglogs)
Toggle("Enable Trace Logs", isOn: $config.tracelogs)
}
Section(header: Title("CPU Mode")) {
HStack {
Spacer()
Picker("Memory Manager Mode", selection: $config.memoryManagerMode) {
ForEach(memoryManagerModes, id: \.0) { key, displayName in
Text(displayName).tag(key)
}
}
.pickerStyle(MenuPickerStyle()) // Dropdown style
}
}
Section(header: Title("Additional Settings")) {
//TextField("Game Path", text: $config.gamepath)
Text("PageSize \(String(Int(getpagesize())))")
TextField("Additional Arguments", text: Binding(
get: {
config.additionalArgs.joined(separator: ", ")
},
set: { newValue in
config.additionalArgs = newValue.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
}
))
}
}
.padding()
}
.onAppear {
if let configs = loadSettings() {
self.config = configs
print(configs)
}
}
.navigationTitle("Settings")
.navigationBarItems(trailing: Button("Save") {
saveSettings()
})
}
func saveSettings() {
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // Optional: Makes the JSON easier to read
let data = try encoder.encode(config)
let jsonString = String(data: data, encoding: .utf8)
// Save to UserDefaults
UserDefaults.standard.set(jsonString, forKey: "config")
print("Settings saved successfully!")
} catch {
print("Failed to save settings: \(error)")
}
}
}
struct Resolution: View {
@Binding var value: Float
var body: some View {
HStack {
Text("Resolution Scale (Custom):")
Spacer()
Button(action: {
if value > 0.1 { // Prevent values going below 0.1
value -= 0.10
value = round(value * 1000) / 1000 // Round to two decimal places
}
print(value)
}) {
Text("-")
.frame(width: 30, height: 30)
.background(Color.gray.opacity(0.2))
.cornerRadius(5)
}
TextField("", value: $value, formatter: NumberFormatter.floatFormatter)
.multilineTextAlignment(.center)
.frame(width: 60)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.decimalPad)
Button(action: {
value += 0.10
value = round(value * 1000) / 1000 // Round to two decimal places
print(value)
}) {
Text("+")
.frame(width: 30, height: 30)
.background(Color.gray.opacity(0.2))
.cornerRadius(5)
}
}
}
}
extension NumberFormatter {
static var floatFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.allowsFloats = true
return formatter
}
}
struct Title: View {
let string: String
init(_ string: String) {
self.string = string
}
var body: some View {
VStack {
Text(string)
.font(.title2)
Divider()
}
}
}

View File

@ -0,0 +1,11 @@
//
// dotnet.xcconfig
// MeloNX
//
// Created by June P on 12/25/24.
//
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
DOTNET_PATH = $(HOME)/.dotnet/dotnet

View File

@ -51,6 +51,59 @@ using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ARMeilleure.Translation;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Audio.Integration;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Renderer;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using Silk.NET.Vulkan;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using Image = SixLabors.ImageSharp.Image;

View File

@ -63,8 +63,16 @@ namespace Ryujinx.Common.Configuration
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
}
string userProfilePath;
if (OperatingSystem.IsIOS())
{
userProfilePath = appDataPath;
}
else
{
userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
}
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
if (Directory.Exists(portablePath))

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>

View File

@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
_textures.AsSpan().Fill(initialImageInfo);
_images.AsSpan().Fill(initialImageInfo);
if (gd.Capabilities.SupportsNullDescriptors && !OperatingSystem.IsIOS())
if (gd.Capabilities.SupportsNullDescriptors)
{
// If null descriptors are supported, we can pass null as the handle.
_dummyBuffer = null;

View File

@ -103,13 +103,9 @@ namespace Ryujinx.Graphics.Vulkan
SupportsShaderStencilExport = supportsShaderStencilExport;
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
SupportsConditionalRendering = supportsConditionalRendering;
if (OperatingSystem.IsIOS()) {
SupportsExtendedDynamicState = (OperatingSystem.IsOSPlatformVersionAtLeast("iOS", 17) ? supportsExtendedDynamicState : false);
} else {
SupportsExtendedDynamicState = supportsExtendedDynamicState;
}
SupportsExtendedDynamicState = (OperatingSystem.IsIOS() ? OperatingSystem.IsIOSVersionAtLeast(17) ? supportsExtendedDynamicState : false : supportsExtendedDynamicState);
SupportsMultiView = supportsMultiView;
SupportsNullDescriptors = (OperatingSystem.IsIOS() ? false : supportsNullDescriptors);
SupportsNullDescriptors = supportsNullDescriptors;
SupportsPushDescriptors = supportsPushDescriptors;
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;

View File

@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
config.UseMetalArgumentBuffers = true;
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
config.SynchronousQueueSubmits = false;
// config.SynchronousQueueSubmits = false;
config.ResumeLostDevice = true;

View File

@ -301,6 +301,10 @@ namespace Ryujinx.Graphics.Vulkan
properties.Limits.FramebufferDepthSampleCounts &
properties.Limits.FramebufferStencilSampleCounts;
bool isDynamicStateSupported = OperatingSystem.IsIOS()
? OperatingSystem.IsIOSVersionAtLeast(17) && _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName)
: _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName);
Capabilities = new HardwareCapabilities(
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
supportsCustomBorderColor,
@ -316,9 +320,9 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
features2.Features.ShaderStorageImageMultisample,
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
isDynamicStateSupported,
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || !IsMoltenVk,
!IsMoltenVk ? featuresRobustness2.NullDescriptor : false,
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,

View File

@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Applets
{
internal class SoftwareKeyboardApplet : IApplet
{
private const string DefaultInputText = "Ryujinx";
private const string DefaultInputText = "MeloNX";
private const int StandardBufferSize = 0x7D8;
private const int InteractiveBufferSize = 0x7D4;
@ -180,6 +180,10 @@ namespace Ryujinx.HLE.HOS.Applets
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
}
[DllImport("SoftwareKeyboard.framework/SoftwareKeyboard", EntryPoint = "displayInputDialog", CallingConvention = CallingConvention.Cdecl)]
public static extern void DisplayInputDialog(ref SoftwareKeyboardUiArgs args, out IntPtr userInput);
private void ExecuteForegroundKeyboard()
{
string initialText = null;
@ -188,23 +192,57 @@ namespace Ryujinx.HLE.HOS.Applets
// InitialStringOffset points to the memory offset and InitialStringLength is the number of UTF-16 characters
if (_transferMemory != null && _keyboardForegroundConfig.InitialStringLength > 0)
{
initialText = Encoding.Unicode.GetString(_transferMemory, _keyboardForegroundConfig.InitialStringOffset,
initialText = Encoding.Unicode.GetString(
_transferMemory,
_keyboardForegroundConfig.InitialStringOffset,
2 * _keyboardForegroundConfig.InitialStringLength);
}
// If the max string length is 0, we set it to a large default
// length.
// If the max string length is 0, we set it to a large default length.
if (_keyboardForegroundConfig.StringLengthMax == 0)
{
_keyboardForegroundConfig.StringLengthMax = 100;
}
// If no GUI handler is set, fallback to default behavior.
if (_device.UiHandler == null)
{
Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default");
_textValue = DefaultInputText;
_lastResult = KeyboardResult.Accept;
// Prepare the SoftwareKeyboardUiArgs struct
var args = new SoftwareKeyboardUiArgs
{
KeyboardMode = _keyboardForegroundConfig.Mode,
HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
SubmitText = !string.IsNullOrWhiteSpace(_keyboardForegroundConfig.SubmitText)
? _keyboardForegroundConfig.SubmitText
: "OK",
StringLengthMin = _keyboardForegroundConfig.StringLengthMin,
StringLengthMax = _keyboardForegroundConfig.StringLengthMax,
InitialText = initialText,
};
IntPtr userInputPtr;
DisplayInputDialog(ref args, out userInputPtr);
if (userInputPtr != IntPtr.Zero)
{
// Convert the IntPtr to a string
string userInput = Marshal.PtrToStringAnsi(userInputPtr);
_textValue = userInput ?? DefaultInputText;
_lastResult = KeyboardResult.Accept;
Console.WriteLine($"User input: {userInput}");
}
else
{
Console.WriteLine("No input was received or input was canceled.");
_textValue = DefaultInputText;
_lastResult = KeyboardResult.Cancel;
}
}
else
{
@ -226,46 +264,36 @@ namespace Ryujinx.HLE.HOS.Applets
_textValue ??= initialText ?? DefaultInputText;
}
// If the game requests a string with a minimum length less
// than our default text, repeat our default text until we meet
// the minimum length requirement.
// This should always be done before the text truncation step.
// Ensure the text meets the minimum length requirement
while (_textValue.Length < _keyboardForegroundConfig.StringLengthMin)
{
_textValue = String.Join(" ", _textValue, _textValue);
_textValue = string.Join(" ", _textValue, _textValue);
}
// If our default text is longer than the allowed length,
// we truncate it.
// Truncate the text if it exceeds the maximum length
if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax)
{
_textValue = _textValue[.._keyboardForegroundConfig.StringLengthMax];
}
// Does the application want to validate the text itself?
// Handle text validation if required
if (_keyboardForegroundConfig.CheckText)
{
// The application needs to validate the response, so we
// submit it to the interactive output buffer, and poll it
// for validation. Once validated, the application will submit
// back a validation status, which is handled in OnInteractiveDataPushIn.
// Submit text for validation
_foregroundState = SoftwareKeyboardState.ValidationPending;
PushForegroundResponse(true);
}
else
{
// If the application doesn't need to validate the response,
// we push the data to the non-interactive output buffer
// and poll it for completion.
// Submit text as complete
_foregroundState = SoftwareKeyboardState.Complete;
PushForegroundResponse(false);
AppletStateChanged?.Invoke(this, null);
}
}
private void OnInteractiveData(object sender, EventArgs e)
{
// Obtain the validation status response.

View File

@ -116,10 +116,10 @@ private void CreateFonts(string uiThemeFontFamily)
if (OperatingSystem.IsIOS())
{
availableFonts = new string[] {
"Chalkboard",
"Chalkboard", // San Francisco is the default font on iOS
"Chalkboard", // Legacy iOS font
"Chalkboard" // Common system font
"SF Pro",
"New York",
"Helvetica Neue",
"Avenir"
};
}
else

View File

@ -40,7 +40,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
AddUser("RyuPlayer", defaultUserImage, DefaultUserId);
AddUser("MeloNX", defaultUserImage, DefaultUserId);
OpenUser(DefaultUserId);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -31,7 +31,7 @@ namespace Ryujinx.Headless.SDL2
// Input
[Option("correct-ons-controller", Required = false, Default = false, HelpText = "Makes the on-screen controller (iOS) buttons correspond to what they show.")]
[Option("correct-controller", Required = false, Default = false, HelpText = "Makes the on-screen controller (iOS) buttons correspond to what they show.")]
public bool OnScreenCorrespond { get; set; }
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]

View File

@ -29,6 +29,9 @@ using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Ui.Common;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
@ -40,6 +43,57 @@ using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.Gamepad
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using System.Linq;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS;
using Ryujinx.Input.HLE;
using Ryujinx.HLE;
using System;
using System.Runtime.InteropServices;
using Ryujinx.Common.Configuration;
using LibHac.Tools.FsSystem;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Backends.SDL2;
using System.IO;
using LibHac.Common.Keys;
using LibHac.Common;
using LibHac.Ns;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Fs;
using Path = System.IO.Path;
using LibHac;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.Common.Utilities;
using System.Globalization;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Common.Logging.Targets;
using System.Collections.Generic;
using LibHac.Bcat;
using Ryujinx.Ui.App.Common;
using System.Text;
using Ryujinx.HLE.Ui;
using ARMeilleure.Translation;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Microsoft.Win32.SafeHandles;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input.HLE;
using Silk.NET.Vulkan;
using System;
using System.IO;
using System.Runtime.InteropServices;
using SDL2;
public class GamepadInfo
{
@ -93,6 +147,57 @@ namespace Ryujinx.Headless.SDL2
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
public static unsafe int GetFPS()
{
if (_window != null) {
Switch Device = _window.Device;
int intValue = (int)Device.Statistics.GetGameFrameRate();
return intValue;
}
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "initialize")]
public static unsafe void Initialize()
{
AppDataManager.Initialize(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
if (_virtualFileSystem == null)
{
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}
if (_libHacHorizonManager == null)
{
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
}
if (_contentManager == null)
{
_contentManager = new ContentManager(_virtualFileSystem);
}
if (_accountManager == null)
{
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, "");
}
if (_userChannelPersistence == null)
{
_userChannelPersistence = new UserChannelPersistence();
}
}
static void Main(string[] args)
{
// Make process DPI aware for proper window sizing on high-res screens.
@ -137,13 +242,93 @@ namespace Ryujinx.Headless.SDL2
}
[UnmanagedCallersOnly(EntryPoint = "install_firmware")]
public static void InstallFirmwareNative(IntPtr inputPtr)
{
try
{
if (inputPtr == IntPtr.Zero)
{
Console.Error.WriteLine("Error: inputPtr is null.");
return;
}
string inputString = Marshal.PtrToStringAnsi(inputPtr);
if (string.IsNullOrEmpty(inputString))
{
Console.Error.WriteLine("Error: inputString is null or empty.");
return;
}
InstallFirmware(inputString);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error in InstallFirmwareNative: {ex.Message}");
}
}
public static void InstallFirmware(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException("File path cannot be null or empty.", nameof(filePath));
}
if (_contentManager == null)
{
throw new InvalidOperationException("_contentManager is not initialized.");
}
_contentManager.InstallFirmware(filePath);
}
[UnmanagedCallersOnly(EntryPoint = "installed_firmware_version")]
public static IntPtr GetInstalledFirmwareVersionNative()
{
var result = GetInstalledFirmwareVersion();
return Marshal.StringToHGlobalAnsi(result);
}
public static string GetInstalledFirmwareVersion()
{
var version = _contentManager.GetCurrentFirmwareVersion();
if (version != null)
{
return version.VersionString;
}
return String.Empty;
}
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
public static void StopEmulation()
{
if (_window != null)
{
_window.Exit();
_emulationContext.Dispose();
_emulationContext = null;
}
}
[UnmanagedCallersOnly(EntryPoint = "get_game_controllers")]
public static unsafe IntPtr GetGamepadList()
{
List<GamepadInfo> gamepads = new List<GamepadInfo>();
IGamepad gamepad;
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
if (_inputManager == null)
{
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
}
// Collect gamepads from the keyboard driver
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
@ -170,6 +355,466 @@ namespace Ryujinx.Headless.SDL2
return ptr;
}
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
{
if (_virtualFileSystem == null) {
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}
var extension = Marshal.PtrToStringAnsi(extensionPtr);
var stream = OpenFile(descriptor);
var gameInfo = GetGameInfo(stream, extension);
return new GameInfoNative(0, gameInfo.TitleName, 0, gameInfo.Developer, 0, gameInfo.Icon);
}
public static GameInfo? GetGameInfo(Stream gameStream, string extension)
{
var gameInfo = new GameInfo
{
FileSize = gameStream.Length * 0.000000000931,
TitleName = "Unknown",
TitleId = "0000000000000000",
Developer = "Unknown",
Version = "0",
Icon = null
};
const Language TitleLanguage = Language.AmericanEnglish;
BlitStruct<ApplicationControlProperty> controlHolder = new(1);
try
{
try
{
if (extension == "nsp" || extension == "pfs0" || extension == "xci")
{
IFileSystem pfs;
bool isExeFs = false;
if (extension == "xci")
{
Xci xci = new(_virtualFileSystem.KeySet, gameStream.AsStorage());
pfs = xci.OpenPartition(XciPartitionType.Secure);
}
else
{
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(gameStream.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
bool hasMainNca = false;
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
{
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
{
using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
// Some main NCAs don't have a data partition, so check if the partition exists before opening it
if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
{
hasMainNca = true;
break;
}
}
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
{
isExeFs = true;
}
}
if (!hasMainNca && !isExeFs)
{
return null;
}
}
if (isExeFs)
{
using UniqueRef<IFile> npdmFile = new();
LibHac.Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
if (ResultFs.PathNotFound.Includes(result))
{
Npdm npdm = new(npdmFile.Get.AsStream());
gameInfo.TitleName = npdm.TitleName;
gameInfo.TitleId = npdm.Aci0.TitleId.ToString("x16");
}
}
else
{
GetControlFsAndTitleId(pfs, out IFileSystem? controlFs, out string? id);
gameInfo.TitleId = id;
if (controlFs == null)
{
Logger.Error?.Print(LogClass.Application, $"No control FS was returned. Unable to process game any further: {gameInfo.TitleName}");
return null;
}
// Check if there is an update available.
if (IsUpdateApplied(gameInfo.TitleId, out IFileSystem? updatedControlFs))
{
// Replace the original ControlFs by the updated one.
controlFs = updatedControlFs;
}
ReadControlData(controlFs, controlHolder.ByteSpan);
GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out gameInfo.TitleId, out gameInfo.Developer, out gameInfo.Version);
// Read the icon from the ControlFS and store it as a byte array
try
{
using UniqueRef<IFile> icon = new();
controlFs?.OpenFile(ref icon.Ref, $"/icon_{TitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
using MemoryStream stream = new();
icon.Get.AsStream().CopyTo(stream);
gameInfo.Icon = stream.ToArray();
}
catch (HorizonResultException)
{
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
{
if (entry.Name == "control.nacp")
{
continue;
}
using var icon = new UniqueRef<IFile>();
controlFs?.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
using MemoryStream stream = new();
icon.Get.AsStream().CopyTo(stream);
gameInfo.Icon = stream.ToArray();
if (gameInfo.Icon != null)
{
break;
}
}
}
}
}
else if (extension == "nro")
{
BinaryReader reader = new(gameStream);
byte[] Read(long position, int size)
{
gameStream.Seek(position, SeekOrigin.Begin);
return reader.ReadBytes(size);
}
gameStream.Seek(24, SeekOrigin.Begin);
int assetOffset = reader.ReadInt32();
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
{
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
ulong nacpOffset = reader.ReadUInt64();
ulong nacpSize = reader.ReadUInt64();
// Reads and stores game icon as byte array
if (iconSize > 0)
{
gameInfo.Icon = Read(assetOffset + iconOffset, (int)iconSize);
}
// Read the NACP data
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out gameInfo.TitleId, out gameInfo.Developer, out gameInfo.Version);
}
}
}
catch (MissingKeyException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
}
catch (InvalidDataException exception)
{
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. {exception}");
}
catch (Exception exception)
{
Logger.Warning?.Print(LogClass.Application, $"The gameStream encountered was not of a valid type. Error: {exception}");
return null;
}
}
catch (IOException exception)
{
Logger.Warning?.Print(LogClass.Application, exception.Message);
}
void ReadControlData(IFileSystem? controlFs, Span<byte> outProperty)
{
using UniqueRef<IFile> controlFile = new();
controlFs?.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
}
void GetGameInformation(ref ApplicationControlProperty controlData, out string? titleName, out string titleId, out string? publisher, out string? version)
{
_ = Enum.TryParse(TitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
{
titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
}
else
{
titleName = null;
publisher = null;
}
if (string.IsNullOrWhiteSpace(titleName))
{
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!controlTitle.NameString.IsEmpty())
{
titleName = controlTitle.NameString.ToString();
break;
}
}
}
if (string.IsNullOrWhiteSpace(publisher))
{
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
{
if (!controlTitle.PublisherString.IsEmpty())
{
publisher = controlTitle.PublisherString.ToString();
break;
}
}
}
if (controlData.PresenceGroupId != 0)
{
titleId = controlData.PresenceGroupId.ToString("x16");
}
else if (controlData.SaveDataOwnerId != 0)
{
titleId = controlData.SaveDataOwnerId.ToString();
}
else if (controlData.AddOnContentBaseId != 0)
{
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
}
else
{
titleId = "0000000000000000";
}
version = controlData.DisplayVersionString.ToString();
}
void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem? controlFs, out string? titleId)
{
(_, _, Nca? controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
if (controlNca == null)
{
Logger.Warning?.Print(LogClass.Application, "Control NCA is null. Unable to load control FS.");
}
// Return the ControlFS
controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
titleId = controlNca?.Header.TitleId.ToString("x16");
}
(Nca? mainNca, Nca? patchNca, Nca? controlNca) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
{
Nca? mainNca = null;
Nca? patchNca = null;
Nca? controlNca = null;
fileSystem.ImportTickets(pfs);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
Logger.Info?.Print(LogClass.Application, $"Loading file from PFS: {fileEntry.FullPath}");
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
if (ncaProgramIndex != programIndex)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.Program)
{
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
patchNca = nca;
}
else
{
mainNca = nca;
}
}
else if (nca.Header.ContentType == NcaContentType.Control)
{
controlNca = nca;
}
}
return (mainNca, patchNca, controlNca);
}
bool IsUpdateApplied(string? titleId, out IFileSystem? updatedControlFs)
{
updatedControlFs = null;
string? updatePath = "(unknown)";
if (_virtualFileSystem == null)
{
Logger.Error?.Print(LogClass.Application, "SwitchDevice was not initialized.");
return false;
}
try
{
(Nca? patchNca, Nca? controlNca) = GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
if (patchNca != null && controlNca != null)
{
updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
return true;
}
}
catch (InvalidDataException)
{
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
}
catch (MissingKeyException exception)
{
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
}
return false;
}
(Nca? patch, Nca? control) GetGameUpdateData(VirtualFileSystem fileSystem, string? titleId, int programIndex, out string? updatePath)
{
updatePath = "";
if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
{
// Clear the program index part.
titleIdBase &= ~0xFUL;
// Load update information if exists.
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
if (File.Exists(titleUpdateMetadataPath))
{
// updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected;
if (File.Exists(updatePath))
{
FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
PartitionFileSystem nsp = new();
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
}
}
}
return (null, null);
}
(Nca? patchNca, Nca? controlNca) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
{
Nca? patchNca = null;
Nca? controlNca = null;
fileSystem.ImportTickets(pfs);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
if (ncaProgramIndex != programIndex)
{
continue;
}
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
{
break;
}
if (nca.Header.ContentType == NcaContentType.Program)
{
patchNca = nca;
}
else if (nca.Header.ContentType == NcaContentType.Control)
{
controlNca = nca;
}
}
return (patchNca, controlNca);
}
return gameInfo;
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
{
if (inputId == null)
@ -204,7 +849,11 @@ namespace Ryujinx.Headless.SDL2
{
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
return null;
inputId = "0";
gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
isKeyboard = true;
}
}
@ -403,21 +1052,40 @@ namespace Ryujinx.Headless.SDL2
static void Load(Options option)
{
AppDataManager.Initialize(option.BaseDataDir);
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
if (_virtualFileSystem == null)
{
_virtualFileSystem = VirtualFileSystem.CreateInstance();
}
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
if (_libHacHorizonManager == null)
{
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
}
_contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();
if (_contentManager == null)
{
_contentManager = new ContentManager(_virtualFileSystem);
}
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
if (_accountManager == null)
{
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
}
if (_userChannelPersistence == null)
{
_userChannelPersistence = new UserChannelPersistence();
}
if (_inputManager == null)
{
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
}
GraphicsConfig.EnableShaderCache = true;
@ -464,6 +1132,12 @@ namespace Ryujinx.Headless.SDL2
return;
}
if (option.InputPath == "MiiMaker") {
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
option.InputPath = contentPath;
}
_inputConfiguration = new List<InputConfig>();
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
@ -632,7 +1306,7 @@ namespace Ryujinx.Headless.SDL2
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume,
options.UseHypervisor ?? true,
options.UseHypervisor ?? false,
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm);
@ -688,6 +1362,17 @@ namespace Ryujinx.Headless.SDL2
Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
bool isFirmwareTitle = false;
if (path.StartsWith("@SystemContent"))
{
path = VirtualFileSystem.SwitchPathToSystemPath(path);
isFirmwareTitle = true;
}
if (Directory.Exists(path))
{
string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
@ -756,23 +1441,35 @@ namespace Ryujinx.Headless.SDL2
}
break;
default:
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
try
{
if (!_emulationContext.LoadProgram(path))
if (isFirmwareTitle) {
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
if (!_emulationContext.LoadNca(path))
{
_emulationContext.Dispose();
return false;
}
}
catch (ArgumentOutOfRangeException)
{
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
else {
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
try
{
if (!_emulationContext.LoadProgram(path))
{
_emulationContext.Dispose();
_emulationContext.Dispose();
return false;
}
}
catch (ArgumentOutOfRangeException)
{
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
return false;
_emulationContext.Dispose();
return false;
}
}
break;
}
@ -794,5 +1491,83 @@ namespace Ryujinx.Headless.SDL2
return true;
}
private static FileStream OpenFile(int descriptor)
{
var safeHandle = new SafeFileHandle(descriptor, false);
return new FileStream(safeHandle, FileAccess.ReadWrite);
}
public class GameInfo
{
public double FileSize;
public string? TitleName;
public string? TitleId;
public string? Developer;
public string? Version;
public byte[]? Icon;
}
public unsafe struct GameInfoNative
{
public ulong FileSize;
public fixed byte TitleName[512];
public ulong TitleId;
public fixed byte Developer[256];
public uint Version;
public byte* ImageData;
public uint ImageSize;
public GameInfoNative(ulong fileSize, string titleName, ulong titleId, string developer, uint version, byte[] imageData)
{
FileSize = fileSize;
TitleId = titleId;
Version = version;
fixed (byte* developerPtr = Developer)
fixed (byte* titleNamePtr = TitleName)
{
CopyStringToFixedArray(titleName, titleNamePtr, 512);
CopyStringToFixedArray(developer, developerPtr, 256);
}
if (imageData == null || imageData.Length > 4096 * 4096)
{
// throw new ArgumentException("Image data must not exceed 4 MB.");
ImageSize = (uint)0;
ImageData = null;
}
else
{
ImageSize = (uint)imageData.Length;
ImageData = (byte*)Marshal.AllocHGlobal(imageData.Length);
Marshal.Copy(imageData, 0, (IntPtr)ImageData, imageData.Length);
}
}
// Don't forget to free the allocated memory
public void Dispose()
{
if (ImageData != null)
{
Marshal.FreeHGlobal((IntPtr)ImageData);
ImageData = null;
}
}
private static void CopyStringToFixedArray(string source, byte* destination, int length)
{
var span = new Span<byte>(destination, length);
Encoding.UTF8.GetBytes(source, span);
}
private static void CopyArrayToFixedArray(byte[] source, byte* destination, int maxLength)
{
var span = new Span<byte>(destination, maxLength);
source.AsSpan().CopyTo(span);
}
}
}
}

View File

@ -8,11 +8,13 @@
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<TieredPGO>true</TieredPGO>
<PublishAot>true</PublishAot>
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
<AotCompilerOptions>-flto -Ofast -funroll-loops</AotCompilerOptions>
<UseNativeAOTRuntime>true</UseNativeAOTRuntime>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup>
<!-- iOS linking stuff from godot -->
@ -46,7 +48,8 @@
<Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
<ItemGroup>
<LinkerArg Include="-Wl,-ld_classic" />
<!-- <LinkerArg Include="-Wl,-ld_classic" /> -->
<LinkerArg Include="-flto -Ofast" />
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
@ -80,7 +83,7 @@
<ItemGroup>
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<!-- <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> -->
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="false">
@ -97,6 +100,8 @@
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -45,14 +45,14 @@ namespace Ryujinx.Headless.SDL2
_mainThreadActions.Enqueue(action);
}
public NpadManager NpadManager { get; }
public TouchScreenManager TouchScreenManager { get; }
public Switch Device { get; private set; }
public IRenderer Renderer { get; private set; }
public NpadManager NpadManager;
public TouchScreenManager TouchScreenManager;
public Switch Device;
public IRenderer Renderer;
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
protected IntPtr WindowHandle { get; set; }
public IntPtr WindowHandle;
public IHostUiTheme HostUiTheme { get; }
public int Width { get; private set; }
@ -66,18 +66,18 @@ namespace Ryujinx.Headless.SDL2
public ScalingFilter ScalingFilter { get; set; }
public int ScalingFilterLevel { get; set; }
protected SDL2MouseDriver MouseDriver;
public SDL2MouseDriver MouseDriver;
private readonly InputManager _inputManager;
private readonly IKeyboard _keyboardInterface;
private readonly GraphicsDebugLevel _glLogLevel;
private readonly Stopwatch _chrono;
private readonly long _ticksPerFrame;
private readonly CancellationTokenSource _gpuCancellationTokenSource;
private readonly ManualResetEvent _exitEvent;
private readonly ManualResetEvent _gpuDoneEvent;
public ManualResetEvent _exitEvent;
public ManualResetEvent _gpuDoneEvent;
private long _ticks;
private bool _isActive;
public bool _isActive;
private bool _isStopped;
private uint _windowId;

View File

@ -28,141 +28,187 @@ namespace Ryujinx.Memory
[LibraryImport("libc")]
public static partial int vm_remap(IntPtr target_task, IntPtr* target_address, IntPtr size, IntPtr mask, int flags, IntPtr src_task, IntPtr src_address, int copy, int* cur_protection, int* max_protection, int inheritance);
const int MAP_MEM_LEDGER_TAGGED = 0x002000;
const int MAP_MEM_NAMED_CREATE = 0x020000;
const int VM_PROT_READ = 0x01;
const int VM_PROT_WRITE = 0x02;
const int VM_PROT_EXECUTE = 0x04;
const int VM_LEDGER_TAG_DEFAULT = 0x00000001;
const int VM_LEDGER_FLAG_NO_FOOTPRINT = 0x00000001;
const int VM_INHERIT_COPY = 1;
const int VM_INHERIT_DEFAULT = VM_INHERIT_COPY;
const int VM_FLAGS_FIXED = 0x0000;
const int VM_FLAGS_ANYWHERE = 0x0001;
const int VM_FLAGS_OVERWRITE = 0x4000;
const IntPtr TASK_NULL = 0;
public static void ReallocateBlock(IntPtr address, int size)
private static class Flags
{
public const int MAP_MEM_LEDGER_TAGGED = 0x002000;
public const int MAP_MEM_NAMED_CREATE = 0x020000;
public const int VM_PROT_READ = 0x01;
public const int VM_PROT_WRITE = 0x02;
public const int VM_PROT_EXECUTE = 0x04;
public const int VM_LEDGER_TAG_DEFAULT = 0x00000001;
public const int VM_LEDGER_FLAG_NO_FOOTPRINT = 0x00000001;
public const int VM_INHERIT_COPY = 1;
public const int VM_INHERIT_DEFAULT = VM_INHERIT_COPY;
public const int VM_FLAGS_FIXED = 0x0000;
public const int VM_FLAGS_ANYWHERE = 0x0001;
public const int VM_FLAGS_OVERWRITE = 0x4000;
}
private const IntPtr TASK_NULL = 0;
private static readonly IntPtr _selfTask;
private static readonly int DEFAULT_CHUNK_SIZE = 16 * 1024 * 1024;
static MachJitWorkaround()
{
_selfTask = mach_task_self();
}
private static int CalculateOptimalChunkSize(int totalSize)
{
// Dynamically calculate chunk size based on total allocation size
// For smaller allocations, use smaller chunks to avoid waste
if (totalSize <= DEFAULT_CHUNK_SIZE)
{
return totalSize;
}
int chunkCount = Math.Max(4, totalSize / DEFAULT_CHUNK_SIZE);
return (totalSize + chunkCount - 1) / chunkCount;
}
private static void HandleMachError(int error, string operation)
{
if (error != 0)
{
throw new InvalidOperationException($"Mach operation '{operation}' failed with error: {error}");
}
}
private static IntPtr ReallocateBlock(IntPtr address, int size)
{
IntPtr selfTask = mach_task_self();
IntPtr memorySize = (IntPtr)size;
IntPtr memoryObjectPort = IntPtr.Zero;
int err = mach_make_memory_entry_64(selfTask, &memorySize, 0, MAP_MEM_NAMED_CREATE | MAP_MEM_LEDGER_TAGGED | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, &memoryObjectPort, 0);
if (err != 0)
{
throw new InvalidOperationException($"Make memory entry failed: {err}");
}
try
{
// Create memory entry
HandleMachError(
mach_make_memory_entry_64(
_selfTask,
&memorySize,
IntPtr.Zero,
Flags.MAP_MEM_NAMED_CREATE | Flags.MAP_MEM_LEDGER_TAGGED |
Flags.VM_PROT_READ | Flags.VM_PROT_WRITE | Flags.VM_PROT_EXECUTE,
&memoryObjectPort,
IntPtr.Zero),
"make_memory_entry_64");
if (memorySize != (IntPtr)size)
{
throw new InvalidOperationException($"Created with size {memorySize} instead of {size}.");
throw new InvalidOperationException($"Memory allocation size mismatch. Requested: {size}, Allocated: {(long)memorySize}");
}
err = mach_memory_entry_ownership(memoryObjectPort, TASK_NULL, VM_LEDGER_TAG_DEFAULT, VM_LEDGER_FLAG_NO_FOOTPRINT);
if (err != 0)
{
throw new InvalidOperationException($"Failed to set ownership: {err}");
}
// Set ownership
HandleMachError(
mach_memory_entry_ownership(
memoryObjectPort,
TASK_NULL,
Flags.VM_LEDGER_TAG_DEFAULT,
Flags.VM_LEDGER_FLAG_NO_FOOTPRINT),
"memory_entry_ownership");
IntPtr mapAddress = address;
err = vm_map(
selfTask,
&mapAddress,
memorySize,
/*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 (err != 0)
{
throw new InvalidOperationException($"Failed to map: {err}");
}
// Map memory
HandleMachError(
vm_map(
_selfTask,
&mapAddress,
memorySize,
IntPtr.Zero,
Flags.VM_FLAGS_OVERWRITE,
memoryObjectPort,
IntPtr.Zero,
0,
Flags.VM_PROT_READ | Flags.VM_PROT_WRITE,
Flags.VM_PROT_READ | Flags.VM_PROT_WRITE | Flags.VM_PROT_EXECUTE,
Flags.VM_INHERIT_COPY),
"vm_map");
if (address != mapAddress)
{
throw new InvalidOperationException($"Remap changed address");
throw new InvalidOperationException("Memory mapping address mismatch");
}
return mapAddress;
}
finally
{
//mach_port_deallocate(selfTask, memoryObjectPort);
// Proper cleanup of memory object port
if (memoryObjectPort != IntPtr.Zero)
{
// mach_port_deallocate(_selfTask, memoryObjectPort);
}
}
Console.WriteLine($"Reallocated an area... {address:x16}");
}
public static void ReallocateAreaWithOwnership(IntPtr address, int size)
{
int mapChunkSize = 128 * 1024 * 1024;
int chunkSize = CalculateOptimalChunkSize(size);
IntPtr currentAddress = address;
IntPtr endAddress = address + size;
IntPtr blockAddress = address;
while (blockAddress < endAddress)
while (currentAddress < endAddress)
{
int blockSize = Math.Min(mapChunkSize, (int)(endAddress - blockAddress));
ReallocateBlock(blockAddress, blockSize);
blockAddress += blockSize;
int blockSize = Math.Min(chunkSize, (int)(endAddress - currentAddress));
ReallocateBlock(currentAddress, blockSize);
currentAddress += blockSize;
}
}
public static IntPtr AllocateSharedMemory(ulong size, bool reserve)
{
IntPtr address = 0;
int err = vm_allocate(mach_task_self(), &address, (IntPtr)size, VM_FLAGS_ANYWHERE);
if (err != 0)
{
throw new InvalidOperationException($"Failed to allocate shared memory: {err}");
}
IntPtr address = IntPtr.Zero;
HandleMachError(
vm_allocate(_selfTask, &address, (IntPtr)size, Flags.VM_FLAGS_ANYWHERE),
"vm_allocate");
return address;
}
public static void DestroySharedMemory(IntPtr handle, ulong size)
{
vm_deallocate(mach_task_self(), handle, (IntPtr)size);
if (handle != IntPtr.Zero && size > 0)
{
vm_deallocate(_selfTask, handle, (IntPtr)size);
}
}
public static IntPtr MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, ulong size)
{
IntPtr taskSelf = mach_task_self();
if (size == 0 || sharedMemory == IntPtr.Zero)
{
throw new ArgumentException("Invalid mapping parameters");
}
IntPtr srcAddress = (IntPtr)((ulong)sharedMemory + srcOffset);
IntPtr dstAddress = location;
int curProtection = 0;
int maxProtection = 0;
int cur_protection = 0;
int max_protection = 0;
int err = vm_remap(taskSelf, &dstAddress, (IntPtr)size, 0, VM_FLAGS_OVERWRITE, taskSelf, srcAddress, 0, &cur_protection, &max_protection, VM_INHERIT_DEFAULT);
if (err != 0)
{
throw new InvalidOperationException($"Failed to allocate remap memory: {err}");
}
HandleMachError(
vm_remap(
_selfTask,
&dstAddress,
(IntPtr)size,
IntPtr.Zero,
Flags.VM_FLAGS_OVERWRITE,
_selfTask,
srcAddress,
0,
&curProtection,
&maxProtection,
Flags.VM_INHERIT_DEFAULT),
"vm_remap");
return dstAddress;
}
public static void UnmapView(IntPtr location, ulong size)
{
vm_deallocate(mach_task_self(), location, (IntPtr)size);
if (location != IntPtr.Zero && size > 0)
{
vm_deallocate(_selfTask, location, (IntPtr)size);
}
}
}
}

View File

@ -37,8 +37,8 @@ namespace Ryujinx.Tests.Cpu
{
int pageBits = (int)ulong.Log2(Size);
_ram = new MemoryBlock(Size * 2);
_memory = new MemoryManager(_ram, 1ul << (pageBits + 4));
_ram = new MemoryBlock(Size);
_memory = new MemoryManager(_ram, 1ul << (pageBits + 2));
_memory.IncrementReferenceCount();
// Some tests depends on hardcoded address that were computed for 4KiB.