forked from MeloNX/MeloNX
Compare commits
72 Commits
greem-melo
...
XC-ios-ht
Author | SHA1 | Date | |
---|---|---|---|
|
27aaea0d68 | ||
|
994f6c0732 | ||
|
c5131d9850 | ||
|
09a757c445 | ||
|
71551adf2d | ||
|
d13dc50a10 | ||
|
2901f462aa | ||
|
160a58e127 | ||
|
9eae1ab594 | ||
|
d2e406fa56 | ||
|
054cb50b22 | ||
|
05b131b33f | ||
|
ccf89aa324 | ||
|
ace6616067 | ||
|
0968360e08 | ||
|
81941f9e9f | ||
|
6e7e5dbfca | ||
|
e76e927b28 | ||
|
b6bad055a8 | ||
|
2fbe6eb9da | ||
|
86c93fe163 | ||
|
2e6e4eb2a0 | ||
|
438c1a896f | ||
|
c5736f9b15 | ||
|
1662bcbc96 | ||
|
63427eb744 | ||
|
06f3c6d20e | ||
|
3e657d7229 | ||
|
ec16e150f6 | ||
|
aca5ee8305 | ||
|
a61e2a3992 | ||
1735216de6 | |||
20547bc412 | |||
|
ed027f1649 | ||
|
e02037d9c3 | ||
|
e74ab3a602 | ||
|
7025c32c4a | ||
|
de19cc29d8 | ||
|
f55d596688 | ||
|
209d0f1a15 | ||
|
db86daef39 | ||
|
9e09cb5767 | ||
b089fda22d | |||
|
94dc643f26 | ||
|
e81ee8f8bf | ||
|
fdbcc483b3 | ||
|
5163737886 | ||
|
6a45d469db | ||
|
658bdd7bec | ||
|
d64ef5eed9 | ||
|
11c3d31764 | ||
|
61344e731e | ||
|
ddcb7a8f77 | ||
|
531446a6ce | ||
|
249e7104f6 | ||
|
3e0c86b047 | ||
|
51a2dfd27d | ||
|
31b10799a3 | ||
|
11ec203e9f | ||
|
de6c0a43b0 | ||
|
663ec73028 | ||
|
aed7a06f0d | ||
|
e0785922d5 | ||
|
abcad02f3e | ||
a5a543f06c | |||
|
c000541be1 | ||
|
4149c329ea | ||
|
300efe5f55 | ||
|
bb4e7314a5 | ||
|
73f14cf59c | ||
|
464f14f143 | ||
|
860d4d363d |
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
|
|
||||||
|
dotnet.xcconfig
|
||||||
|
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
[Rr]elease/
|
[Rr]elease/
|
||||||
x64/
|
x64/
|
||||||
|
33
Compile.md
Normal file
33
Compile.md
Normal 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
|
||||||
|
```
|
@ -24,13 +24,13 @@ namespace ARMeilleure.Native
|
|||||||
public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
|
public static unsafe void Copy(IntPtr dst, IntPtr src, ulong n) {
|
||||||
// When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
|
// When NativeAOT is in use, we can toggle per-thread write protection without worrying about breaking .NET code.
|
||||||
|
|
||||||
//pthread_jit_write_protect_np(0);
|
// pthread_jit_write_protect_np(0);
|
||||||
|
|
||||||
var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
|
var srcSpan = new Span<byte>(src.ToPointer(), (int)n);
|
||||||
var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
|
var dstSpan = new Span<byte>(dst.ToPointer(), (int)n);
|
||||||
srcSpan.CopyTo(dstSpan);
|
srcSpan.CopyTo(dstSpan);
|
||||||
|
|
||||||
//pthread_jit_write_protect_np(1);
|
// pthread_jit_write_protect_np(1);
|
||||||
|
|
||||||
// Ensure that the instruction cache for this range is invalidated.
|
// Ensure that the instruction cache for this range is invalidated.
|
||||||
sys_icache_invalidate(dst, (IntPtr)n);
|
sys_icache_invalidate(dst, (IntPtr)n);
|
||||||
|
@ -15,11 +15,11 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
static partial class JitCache
|
static partial class JitCache
|
||||||
{
|
{
|
||||||
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
||||||
private static readonly int _pageMask = _pageSize - 1;
|
private static readonly int _pageMask = _pageSize - 2;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 2047 * 1024 * 1024;
|
private const int CacheSize = 2047 * 1024 * 1024;
|
||||||
private const int CacheSizeIOS = 512 * 1024 * 1024;
|
private const int CacheSizeIOS = 64 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
private static ReservedRegion _jitRegion;
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
@ -3,12 +3,31 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 73;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXAggregateTarget section */
|
||||||
|
BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */ = {
|
||||||
|
isa = PBXAggregateTarget;
|
||||||
|
buildConfigurationList = BD43C6222D1B248D003BBC42 /* Build configuration list for PBXAggregateTarget "com.Stossy11.MeloNX.RyujinxAg" */;
|
||||||
|
buildPhases = (
|
||||||
|
BD43C62A2D1B252F003BBC42 /* ShellScript */,
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = com.Stossy11.MeloNX.RyujinxAg;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = com.Stossy11.MeloNX.RyujinxAg;
|
||||||
|
};
|
||||||
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||||
4E80AA212CD705DD00029585 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E80AA202CD705DD00029585 /* SDL */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -26,6 +45,13 @@
|
|||||||
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
||||||
remoteInfo = MeloNX;
|
remoteInfo = MeloNX;
|
||||||
};
|
};
|
||||||
|
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = BD43C61D2D1B23AB003BBC42;
|
||||||
|
remoteInfo = Ryujinx;
|
||||||
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
@ -46,10 +72,12 @@
|
|||||||
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
||||||
|
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = dotnet.xcconfig.example; sourceTree = "<group>"; };
|
||||||
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */ = {
|
565056492D2A756A00C8BB1E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
Info.plist,
|
Info.plist,
|
||||||
@ -59,33 +87,74 @@
|
|||||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||||
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = {
|
5650564D2D2A75B300C8BB1E /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
|
||||||
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
||||||
attributesByRelativePath = {
|
attributesByRelativePath = {
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
|
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework" = (
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (CodeSignOnCopy, );
|
CodeSignOnCopy,
|
||||||
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (CodeSignOnCopy, );
|
RemoveHeadersOnCopy,
|
||||||
"Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, );
|
);
|
||||||
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (
|
||||||
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
CodeSignOnCopy,
|
||||||
Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
);
|
||||||
Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (
|
||||||
Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
CodeSignOnCopy,
|
||||||
Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
);
|
||||||
Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
"Dependencies/Dynamic Libraries/libavutil.dylib" = (
|
||||||
Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
CodeSignOnCopy,
|
||||||
Dependencies/XCFrameworks/libteakra.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
);
|
||||||
|
Dependencies/XCFrameworks/MoltenVK.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/SDL2.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libSPIRV.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavcodec.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavfilter.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavformat.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libavutil.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libswresample.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libswscale.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
|
Dependencies/XCFrameworks/libteakra.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
|
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
"Dependencies/Dynamic Libraries/libavcodec.dylib",
|
"Dependencies/Dynamic Libraries/libavcodec.dylib",
|
||||||
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework",
|
||||||
Dependencies/XCFrameworks/libavcodec.xcframework,
|
Dependencies/XCFrameworks/libavcodec.xcframework,
|
||||||
Dependencies/XCFrameworks/libavfilter.xcframework,
|
Dependencies/XCFrameworks/libavfilter.xcframework,
|
||||||
Dependencies/XCFrameworks/libavformat.xcframework,
|
Dependencies/XCFrameworks/libavformat.xcframework,
|
||||||
Dependencies/XCFrameworks/libavutil.xcframework,
|
Dependencies/XCFrameworks/libavutil.xcframework,
|
||||||
|
Dependencies/XCFrameworks/libSPIRV.xcframework,
|
||||||
Dependencies/XCFrameworks/libswresample.xcframework,
|
Dependencies/XCFrameworks/libswresample.xcframework,
|
||||||
Dependencies/XCFrameworks/libswscale.xcframework,
|
Dependencies/XCFrameworks/libswscale.xcframework,
|
||||||
Dependencies/XCFrameworks/libteakra.xcframework,
|
Dependencies/XCFrameworks/libteakra.xcframework,
|
||||||
@ -96,25 +165,9 @@
|
|||||||
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */ = {
|
4E80A98F2CD6F54500029585 /* MeloNX */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (565056492D2A756A00C8BB1E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 5650564D2D2A75B300C8BB1E /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MeloNX; sourceTree = "<group>"; };
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXTests; sourceTree = "<group>"; };
|
||||||
exceptions = (
|
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXUITests; sourceTree = "<group>"; };
|
||||||
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
|
|
||||||
4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */,
|
|
||||||
);
|
|
||||||
path = MeloNX;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = MeloNXTests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
path = MeloNXUITests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -122,8 +175,9 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4E551F202CF128540096A2DF /* GameController.framework in Frameworks */,
|
4E4854022D138D7600A446A6 /* GameController.framework in Frameworks */,
|
||||||
4E80AA212CD705DD00029585 /* SDL in Frameworks */,
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
||||||
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -147,6 +201,8 @@
|
|||||||
4E80A9842CD6F54500029585 = {
|
4E80A9842CD6F54500029585 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */,
|
||||||
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
||||||
4E80A9AA2CD6F54700029585 /* MeloNXUITests */,
|
4E80A9AA2CD6F54700029585 /* MeloNXUITests */,
|
||||||
@ -175,6 +231,25 @@
|
|||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXLegacyTarget section */
|
||||||
|
BD43C61D2D1B23AB003BBC42 /* Ryujinx */ = {
|
||||||
|
isa = PBXLegacyTarget;
|
||||||
|
buildArgumentsString = "publish -c Release -r ios-arm64 -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true";
|
||||||
|
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
|
||||||
|
buildPhases = (
|
||||||
|
);
|
||||||
|
buildToolPath = "$(DOTNET_PATH)";
|
||||||
|
buildWorkingDirectory = "$(SRCROOT)/../..";
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Ryujinx;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
passBuildSettingsInEnvironment = 0;
|
||||||
|
productName = Ryujinx;
|
||||||
|
};
|
||||||
|
/* End PBXLegacyTarget section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
4E80A98C2CD6F54500029585 /* MeloNX */ = {
|
4E80A98C2CD6F54500029585 /* MeloNX */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
@ -194,7 +269,8 @@
|
|||||||
);
|
);
|
||||||
name = MeloNX;
|
name = MeloNX;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
4E80AA202CD705DD00029585 /* SDL */,
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */,
|
||||||
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
||||||
);
|
);
|
||||||
productName = MeloNX;
|
productName = MeloNX;
|
||||||
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
|
||||||
@ -267,6 +343,12 @@
|
|||||||
CreatedOnToolsVersion = 16.1;
|
CreatedOnToolsVersion = 16.1;
|
||||||
TestTargetID = 4E80A98C2CD6F54500029585;
|
TestTargetID = 4E80A98C2CD6F54500029585;
|
||||||
};
|
};
|
||||||
|
BD43C61D2D1B23AB003BBC42 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
|
BD43C6212D1B248D003BBC42 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 4E80A9882CD6F54500029585 /* Build configuration list for PBXProject "MeloNX" */;
|
buildConfigurationList = 4E80A9882CD6F54500029585 /* Build configuration list for PBXProject "MeloNX" */;
|
||||||
@ -279,9 +361,10 @@
|
|||||||
mainGroup = 4E80A9842CD6F54500029585;
|
mainGroup = 4E80A9842CD6F54500029585;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */,
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */,
|
||||||
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 56;
|
||||||
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@ -289,6 +372,8 @@
|
|||||||
4E80A98C2CD6F54500029585 /* MeloNX */,
|
4E80A98C2CD6F54500029585 /* MeloNX */,
|
||||||
4E80A99C2CD6F54700029585 /* MeloNXTests */,
|
4E80A99C2CD6F54700029585 /* MeloNXTests */,
|
||||||
4E80A9A62CD6F54700029585 /* MeloNXUITests */,
|
4E80A9A62CD6F54700029585 /* MeloNXUITests */,
|
||||||
|
BD43C61D2D1B23AB003BBC42 /* Ryujinx */,
|
||||||
|
BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -298,6 +383,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
5650564B2D2A758600C8BB1E /* dotnet.xcconfig.example in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -317,6 +403,28 @@
|
|||||||
};
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
BD43C62A2D1B252F003BBC42 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"$(SRCROOT)/../../src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib",
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(SRCROOT)/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
4E80A9892CD6F54500029585 /* Sources */ = {
|
4E80A9892CD6F54500029585 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
@ -352,6 +460,11 @@
|
|||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
|
||||||
|
targetProxy = BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
@ -360,6 +473,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
@ -390,17 +504,20 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
EAGER_LINKING = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_FAST_MATH = YES;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
);
|
);
|
||||||
|
GCC_UNROLL_LOOPS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
@ -408,12 +525,14 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -423,6 +542,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_OPTIMIZATION = time;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
@ -453,11 +573,15 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
EAGER_LINKING = YES;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GCC_FAST_MATH = YES;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
|
GCC_UNROLL_LOOPS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
@ -465,11 +589,15 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_DISABLE_SAFETY_CHECKS = YES;
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@ -485,16 +613,29 @@
|
|||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
ENABLE_TESTABILITY = NO;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
);
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -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/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_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@ -576,17 +839,29 @@
|
|||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 3;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
);
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -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/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_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
@ -729,6 +1126,52 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
BD43C61F2D1B23AB003BBC42 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEBUGGING_SYMBOLS = YES;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
OTHER_CFLAGS = "";
|
||||||
|
OTHER_LDFLAGS = "";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
BD43C6202D1B23AB003BBC42 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
OTHER_CFLAGS = "";
|
||||||
|
OTHER_LDFLAGS = "";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
BD43C6232D1B248D003BBC42 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
BD43C6242D1B248D003BBC42 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
@ -768,24 +1211,55 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
BD43C61F2D1B23AB003BBC42 /* Debug */,
|
||||||
|
BD43C6202D1B23AB003BBC42 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
BD43C6222D1B248D003BBC42 /* Build configuration list for PBXAggregateTarget "com.Stossy11.MeloNX.RyujinxAg" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
BD43C6232D1B248D003BBC42 /* Debug */,
|
||||||
|
BD43C6242D1B248D003BBC42 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = {
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/ctreffs/SwiftSDL2";
|
repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 1.4.1;
|
minimumVersion = 1.5.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/mchoe/SwiftSVG";
|
||||||
|
requirement = {
|
||||||
|
branch = master;
|
||||||
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
4E80AA202CD705DD00029585 /* SDL */ = {
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 4E80AA1F2CD705DD00029585 /* XCRemoteSwiftPackageReference "SwiftSDL2" */;
|
package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */;
|
||||||
productName = SDL;
|
productName = SwiftUIJoystick;
|
||||||
|
};
|
||||||
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
||||||
|
productName = SwiftSVG;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5",
|
"originHash" : "1b46f7a56d6f994a826e31441c25b929398800cf38b3e9be23ae6e0ef8fc32c7",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "swiftsdl2",
|
"identity" : "swiftsvg",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/ctreffs/SwiftSDL2",
|
"location" : "https://github.com/mchoe/SwiftSVG",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a",
|
"branch" : "master",
|
||||||
"version" : "1.4.1"
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftuijoystick",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
|
||||||
|
"version" : "1.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<array/>
|
||||||
|
</plist>
|
Binary file not shown.
Binary file not shown.
BIN
src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/ls.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
BIN
src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/ls.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -7,32 +7,33 @@
|
|||||||
<BreakpointProxy
|
<BreakpointProxy
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
<BreakpointContent
|
<BreakpointContent
|
||||||
uuid = "1F3099D0-0456-4AD5-8EA1-52BABAF2AA89"
|
uuid = "499F5405-B63B-4623-9332-1E44FC449FD0"
|
||||||
shouldBeEnabled = "Yes"
|
shouldBeEnabled = "No"
|
||||||
nameForDebugger = "Ignore-SIGUSR"
|
|
||||||
ignoreCount = "0"
|
ignoreCount = "0"
|
||||||
continueAfterRunningActions = "Yes"
|
continueAfterRunningActions = "No"
|
||||||
filePath = "MeloNX/MeloNXApp.swift"
|
filePath = "MeloNX/Views/GamesList/GameListView.swift"
|
||||||
startingColumnNumber = "9223372036854775807"
|
startingColumnNumber = "9223372036854775807"
|
||||||
endingColumnNumber = "9223372036854775807"
|
endingColumnNumber = "9223372036854775807"
|
||||||
startingLineNumber = "14"
|
startingLineNumber = "309"
|
||||||
endingLineNumber = "14"
|
endingLineNumber = "309"
|
||||||
landmarkName = "body"
|
landmarkName = "loadGames()"
|
||||||
landmarkType = "24">
|
landmarkType = "7">
|
||||||
<Actions>
|
</BreakpointContent>
|
||||||
<BreakpointActionProxy
|
</BreakpointProxy>
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
<BreakpointProxy
|
||||||
<ActionContent
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
consoleCommand = "process handle SIGUSR1 -s false -n false">
|
<BreakpointContent
|
||||||
</ActionContent>
|
uuid = "0BB7C122-8933-48E8-ABA3-1ABB39594258"
|
||||||
</BreakpointActionProxy>
|
shouldBeEnabled = "No"
|
||||||
<BreakpointActionProxy
|
ignoreCount = "0"
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
continueAfterRunningActions = "No"
|
||||||
<ActionContent
|
filePath = "MeloNX/Models/Game.swift"
|
||||||
consoleCommand = "process handle SIGBUS -s false -n false">
|
startingColumnNumber = "9223372036854775807"
|
||||||
</ActionContent>
|
endingColumnNumber = "9223372036854775807"
|
||||||
</BreakpointActionProxy>
|
startingLineNumber = "37"
|
||||||
</Actions>
|
endingLineNumber = "37"
|
||||||
|
landmarkName = "createImage(from:)"
|
||||||
|
landmarkType = "7">
|
||||||
</BreakpointContent>
|
</BreakpointContent>
|
||||||
</BreakpointProxy>
|
</BreakpointProxy>
|
||||||
</Breakpoints>
|
</Breakpoints>
|
||||||
|
@ -9,6 +9,16 @@
|
|||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
49
src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h
Normal file
49
src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h
Normal 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 */
|
||||||
|
|
25
src/MeloNX/MeloNX/App/Core/JIT/AskForJIT.swift
Normal file
25
src/MeloNX/MeloNX/App/Core/JIT/AskForJIT.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// AskForJIT.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 9/10/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
func askForJIT() {
|
||||||
|
// Check if TrollStore exists by checking the presence of the directory
|
||||||
|
let urlScheme = "apple-magnifier://enable-jit?bundle-id=\(Bundle.main.bundleIdentifier!)"
|
||||||
|
if let launchURL = URL(string: urlScheme) {
|
||||||
|
if UIApplication.shared.canOpenURL(launchURL) {
|
||||||
|
// Open the URL to enable JIT
|
||||||
|
UIApplication.shared.open(launchURL, options: [:], completionHandler: nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
27
src/MeloNX/MeloNX/App/Core/JIT/utils.h
Normal file
27
src/MeloNX/MeloNX/App/Core/JIT/utils.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#if __has_feature(modules)
|
||||||
|
@import UIKit;
|
||||||
|
@import Foundation;
|
||||||
|
#else
|
||||||
|
#import "UIKit/UIKit.h"
|
||||||
|
#import "Foundation/Foundation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DISPATCH_ASYNC_START dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
#define DISPATCH_ASYNC_CLOSE });
|
||||||
|
|
||||||
|
#define PT_TRACE_ME 0
|
||||||
|
extern int ptrace(int, pid_t, caddr_t, int);
|
||||||
|
|
||||||
|
#define CS_DEBUGGED 0x10000000
|
||||||
|
extern int csops(
|
||||||
|
pid_t pid,
|
||||||
|
unsigned int ops,
|
||||||
|
void *useraddr,
|
||||||
|
size_t usersize
|
||||||
|
);
|
||||||
|
|
||||||
|
extern BOOL getEntitlementValue(NSString *key);
|
||||||
|
extern BOOL isJITEnabled(void);
|
||||||
|
|
||||||
|
#define DLOG(format, ...) ShowAlert(@"DEBUG", [NSString stringWithFormat:@"\n %s [Line %d] \n %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:format, ##__VA_ARGS__]])
|
||||||
|
void ShowAlert(NSString* title, NSString* message, _Bool* showok);
|
91
src/MeloNX/MeloNX/App/Core/JIT/utils.m
Normal file
91
src/MeloNX/MeloNX/App/Core/JIT/utils.m
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// FPSMonitor.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class FPSMonitor: ObservableObject {
|
||||||
|
@Published private(set) var currentFPS: UInt64 = 0
|
||||||
|
private var timer: Timer?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateFPS()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateFPS() {
|
||||||
|
let currentfps = UInt64(get_current_fps())
|
||||||
|
|
||||||
|
self.currentFPS = currentfps
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func formatFPS() -> String {
|
||||||
|
let fps = Double(currentFPS)
|
||||||
|
let fpsString = String(format: "FPS: %.2f", fps)
|
||||||
|
|
||||||
|
return fpsString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// MemoryUsageMonitor.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class MemoryUsageMonitor: ObservableObject {
|
||||||
|
@Published private(set) var memoryUsage: UInt64 = 0
|
||||||
|
private var timer: Timer?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateMemoryUsage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateMemoryUsage() {
|
||||||
|
var taskInfo = task_vm_info_data_t()
|
||||||
|
var count = mach_msg_type_number_t(MemoryLayout<task_vm_info>.size) / 4
|
||||||
|
let result: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
|
||||||
|
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
|
||||||
|
task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == KERN_SUCCESS {
|
||||||
|
memoryUsage = taskInfo.phys_footprint
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print("Error with task_info(): " +
|
||||||
|
(String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMemorySize(_ bytes: UInt64) -> String {
|
||||||
|
let formatter = ByteCountFormatter()
|
||||||
|
formatter.allowedUnits = [.useMB, .useGB]
|
||||||
|
formatter.countStyle = .memory
|
||||||
|
return formatter.string(fromByteCount: Int64(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// Untitled.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 21/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PerformanceOverlayView: View {
|
||||||
|
@StateObject private var memorymonitor = MemoryUsageMonitor()
|
||||||
|
|
||||||
|
@StateObject private var fpsmonitor = FPSMonitor()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Text("\(fpsmonitor.formatFPS())")
|
||||||
|
Text(memorymonitor.formatMemorySize(memorymonitor.memoryUsage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
class MTLHud {
|
class MTLHud {
|
||||||
|
|
||||||
var canMetalHud: Bool {
|
var canMetalHud: Bool {
|
@ -7,12 +7,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SDL2
|
|
||||||
import GameController
|
import GameController
|
||||||
|
|
||||||
struct Controller: Identifiable, Hashable {
|
struct Controller: Identifiable, Hashable {
|
||||||
let id: String
|
var id: String
|
||||||
let name: String
|
var name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct iOSNav<Content: View>: View {
|
struct iOSNav<Content: View>: View {
|
||||||
@ -32,13 +31,16 @@ struct iOSNav<Content: View>: View {
|
|||||||
class Ryujinx {
|
class Ryujinx {
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
|
|
||||||
|
let virtualController = VirtualController()
|
||||||
|
|
||||||
@Published var controllerMap: [Controller] = []
|
@Published var controllerMap: [Controller] = []
|
||||||
|
@State var firmwareversion = "0"
|
||||||
|
|
||||||
static let shared = Ryujinx()
|
static let shared = Ryujinx()
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
public struct Configuration : Codable {
|
public struct Configuration : Codable, Equatable {
|
||||||
var gamepath: String
|
var gamepath: String
|
||||||
var inputids: [String]
|
var inputids: [String]
|
||||||
var resscale: Float
|
var resscale: Float
|
||||||
@ -49,7 +51,6 @@ class Ryujinx {
|
|||||||
var listinputids: Bool
|
var listinputids: Bool
|
||||||
var fullscreen: Bool
|
var fullscreen: Bool
|
||||||
var memoryManagerMode: String
|
var memoryManagerMode: String
|
||||||
var disableVSync: Bool
|
|
||||||
var disableShaderCache: Bool
|
var disableShaderCache: Bool
|
||||||
var disableDockedMode: Bool
|
var disableDockedMode: Bool
|
||||||
var enableTextureRecompression: Bool
|
var enableTextureRecompression: Bool
|
||||||
@ -63,7 +64,6 @@ class Ryujinx {
|
|||||||
listinputids: Bool = false,
|
listinputids: Bool = false,
|
||||||
fullscreen: Bool = true,
|
fullscreen: Bool = true,
|
||||||
memoryManagerMode: String = "HostMapped",
|
memoryManagerMode: String = "HostMapped",
|
||||||
disableVSync: Bool = false,
|
|
||||||
disableShaderCache: Bool = false,
|
disableShaderCache: Bool = false,
|
||||||
disableDockedMode: Bool = false,
|
disableDockedMode: Bool = false,
|
||||||
nintendoinput: Bool = true,
|
nintendoinput: Bool = true,
|
||||||
@ -78,7 +78,6 @@ class Ryujinx {
|
|||||||
self.tracelogs = tracelogs
|
self.tracelogs = tracelogs
|
||||||
self.listinputids = listinputids
|
self.listinputids = listinputids
|
||||||
self.fullscreen = fullscreen
|
self.fullscreen = fullscreen
|
||||||
self.disableVSync = disableVSync
|
|
||||||
self.disableShaderCache = disableShaderCache
|
self.disableShaderCache = disableShaderCache
|
||||||
self.disableDockedMode = disableDockedMode
|
self.disableDockedMode = disableDockedMode
|
||||||
self.enableTextureRecompression = enableTextureRecompression
|
self.enableTextureRecompression = enableTextureRecompression
|
||||||
@ -98,10 +97,12 @@ class Ryujinx {
|
|||||||
|
|
||||||
isRunning = true
|
isRunning = true
|
||||||
|
|
||||||
// Start The Emulation on the main thread
|
RunLoop.current.perform {
|
||||||
DispatchQueue.main.async {
|
let url = URL(string: config.gamepath)!
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let args = self.buildCommandLineArgs(from: config)
|
let args = self.buildCommandLineArgs(from: config)
|
||||||
|
let accessing = url.startAccessingSecurityScopedResource()
|
||||||
|
|
||||||
// Convert Arguments to ones that Ryujinx can Read
|
// Convert Arguments to ones that Ryujinx can Read
|
||||||
let cArgs = args.map { strdup($0) }
|
let cArgs = args.map { strdup($0) }
|
||||||
@ -113,6 +114,10 @@ class Ryujinx {
|
|||||||
|
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
self.isRunning = false
|
self.isRunning = false
|
||||||
|
if accessing {
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
throw RyujinxError.executionError(code: result)
|
throw RyujinxError.executionError(code: result)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@ -145,33 +150,35 @@ class Ryujinx {
|
|||||||
args.append("--graphics-backend")
|
args.append("--graphics-backend")
|
||||||
args.append("Vulkan")
|
args.append("Vulkan")
|
||||||
|
|
||||||
// Fixes the Stubs.DispatchLoop Crash
|
|
||||||
args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode])
|
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 {
|
if config.fullscreen {
|
||||||
args.append(contentsOf: ["--exclusive-fullscreen", String(config.fullscreen)])
|
args.append(contentsOf: ["--aspect-ratio", "Stretched"])
|
||||||
args.append(contentsOf: ["--exclusive-fullscreen-width", "1280"])
|
|
||||||
args.append(contentsOf: ["--exclusive-fullscreen-height", "720"])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)])
|
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.nintendoinput {
|
if !config.disableShaderCache { // same with disableShaderCache
|
||||||
args.append("--correct-ons-controller")
|
|
||||||
}
|
|
||||||
if config.enableInternet {
|
|
||||||
args.append("--enable-internet-connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adding default args directly into additionalArgs
|
|
||||||
if config.disableVSync {
|
|
||||||
args.append("--disable-vsync")
|
|
||||||
}
|
|
||||||
if config.disableShaderCache {
|
|
||||||
args.append("--disable-shader-cache")
|
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")
|
args.append("--disable-docked-mode")
|
||||||
}
|
}
|
||||||
if config.enableTextureRecompression {
|
if config.enableTextureRecompression {
|
||||||
@ -203,8 +210,41 @@ class Ryujinx {
|
|||||||
return args
|
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] {
|
func getConnectedControllers() -> [Controller] {
|
||||||
|
|
||||||
|
|
||||||
guard let jsonPtr = get_game_controllers() else {
|
guard let jsonPtr = get_game_controllers() else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -232,6 +272,38 @@ class Ryujinx {
|
|||||||
return controllers
|
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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
309
src/MeloNX/MeloNX/App/Views/ContentView.swift
Normal file
309
src/MeloNX/MeloNX/App/Views/ContentView.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
304
src/MeloNX/MeloNX/App/Views/ControllerView/ControllerView.swift
Normal file
304
src/MeloNX/MeloNX/App/Views/ControllerView/ControllerView.swift
Normal 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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Haptics.swift
|
||||||
|
// Pomelo
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 11/9/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class Haptics {
|
||||||
|
static let shared = Haptics()
|
||||||
|
|
||||||
|
private init() { }
|
||||||
|
|
||||||
|
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||||
|
print("haptics")
|
||||||
|
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// JoystickView.swift
|
||||||
|
// Pomelo
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 30/9/2024.
|
||||||
|
// Copyright © 2024 Stossy11. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftUIJoystick
|
||||||
|
|
||||||
|
public struct Joystick: View {
|
||||||
|
@State var iscool: Bool? = nil
|
||||||
|
|
||||||
|
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||||
|
var dragDiameter: CGFloat {
|
||||||
|
var selfs = CGFloat(160)
|
||||||
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
|
return selfs * 1.2
|
||||||
|
}
|
||||||
|
return selfs
|
||||||
|
}
|
||||||
|
private let shape: JoystickShape = .circle
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack{
|
||||||
|
JoystickBuilder(
|
||||||
|
monitor: self.joystickMonitor,
|
||||||
|
width: self.dragDiameter,
|
||||||
|
shape: .circle,
|
||||||
|
background: {
|
||||||
|
Text("")
|
||||||
|
.hidden()
|
||||||
|
},
|
||||||
|
foreground: {
|
||||||
|
Circle().fill(Color.gray)
|
||||||
|
.opacity(0.7)
|
||||||
|
},
|
||||||
|
locksInPlace: false)
|
||||||
|
.onChange(of: self.joystickMonitor.xyPoint) { newValue in
|
||||||
|
let scaledX = Float(newValue.x)
|
||||||
|
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
||||||
|
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||||
|
|
||||||
|
if iscool != nil {
|
||||||
|
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
||||||
|
} else {
|
||||||
|
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
493
src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift
Normal file
493
src/MeloNX/MeloNX/App/Views/GamesList/GameListView.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
430
src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift
Normal file
430
src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/MeloNX/MeloNX/App/Views/TabView/TabView.swift
Normal file
34
src/MeloNX/MeloNX/App/Views/TabView/TabView.swift
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// TabView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 10/12/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
|
||||||
|
struct MainTabView: View {
|
||||||
|
@Binding var startemu: Game?
|
||||||
|
@Binding var config: Ryujinx.Configuration
|
||||||
|
@Binding var MVKconfig: [MoltenVKSettings]
|
||||||
|
@Binding var controllersList: [Controller]
|
||||||
|
@Binding var currentControllers: [Controller]
|
||||||
|
|
||||||
|
@Binding var onscreencontroller: Controller
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
TabView {
|
||||||
|
GameLibraryView(startemu: $startemu)
|
||||||
|
.tabItem {
|
||||||
|
Label("Games", systemImage: "gamecontroller.fill")
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsView(config: $config, MoltenVKSettings: $MVKconfig, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
|
||||||
|
.tabItem {
|
||||||
|
Label("Settings", systemImage: "gear")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
|
"filename" : "nxgradientpng.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"data" : [
|
||||||
|
{
|
||||||
|
"filename" : "Troll-Face.svg",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"universal-type-identifier" : "public.svg-image"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 26 KiB |
@ -1,25 +0,0 @@
|
|||||||
//
|
|
||||||
// Ryujinx-Header.h
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef RyujinxHeader
|
|
||||||
#define RyujinxHeader
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Declare the main_ryujinx_sdl function, matching the signature
|
|
||||||
int main_ryujinx_sdl(int argc, char **argv);
|
|
||||||
|
|
||||||
const char* get_game_controllers();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* RyujinxSDL_h */
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
|||||||
//
|
|
||||||
// VirtualController.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 28/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GameController
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
public var controllerCallback: (() -> Void)?
|
|
||||||
|
|
||||||
var VirtualController: GCVirtualController!
|
|
||||||
func showVirtualController() {
|
|
||||||
let config = GCVirtualController.Configuration()
|
|
||||||
if UserDefaults.standard.bool(forKey: "RyuDemoControls") {
|
|
||||||
config.elements = [
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
// GCInputRightThumbstick,
|
|
||||||
GCInputRightTrigger,
|
|
||||||
GCInputLeftTrigger,
|
|
||||||
GCInputLeftShoulder,
|
|
||||||
GCInputRightShoulder
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
config.elements = [
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
GCInputRightThumbstick,
|
|
||||||
GCInputRightTrigger,
|
|
||||||
GCInputLeftTrigger,
|
|
||||||
GCInputLeftShoulder,
|
|
||||||
GCInputRightShoulder
|
|
||||||
]
|
|
||||||
}
|
|
||||||
VirtualController = GCVirtualController(configuration: config)
|
|
||||||
VirtualController.connect { err in
|
|
||||||
print("controller connect: \(String(describing: err))")
|
|
||||||
patchMakeKeyAndVisible()
|
|
||||||
if let controllerCallback {
|
|
||||||
controllerCallback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitforcontroller() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
|
||||||
|
|
||||||
if let window = UIApplication.shared.windows.first {
|
|
||||||
// Function to recursively search for GCControllerView
|
|
||||||
func findGCControllerView(in view: UIView) -> UIView? {
|
|
||||||
// Check if current view is GCControllerView
|
|
||||||
if String(describing: type(of: view)) == "GCControllerView" {
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search through subviews
|
|
||||||
for subview in view.subviews {
|
|
||||||
if let found = findGCControllerView(in: subview) {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let gcControllerView = findGCControllerView(in: window) {
|
|
||||||
// Found the GCControllerView
|
|
||||||
print("Found GCControllerView:", gcControllerView)
|
|
||||||
|
|
||||||
if let theWindow = theWindow, (findGCControllerView(in: theWindow) == nil) {
|
|
||||||
theWindow.addSubview(gcControllerView)
|
|
||||||
|
|
||||||
theWindow.bringSubviewToFront(gcControllerView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 15.0, *)
|
|
||||||
func reconnectVirtualController() {
|
|
||||||
VirtualController.disconnect()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
VirtualController.connect { err in
|
|
||||||
print("reconnected: err \(String(describing: err))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
|||||||
//
|
|
||||||
// Untitled.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 28/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GameController
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var theWindow: UIWindow? = nil
|
|
||||||
extension UIWindow {
|
|
||||||
@objc func wdb_makeKeyAndVisible() {
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
|
||||||
}
|
|
||||||
self.wdb_makeKeyAndVisible()
|
|
||||||
theWindow = self
|
|
||||||
if #available(iOS 15.0, *) {
|
|
||||||
reconnectVirtualController()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if let window = theWindow {
|
|
||||||
waitforcontroller()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func patchMakeKeyAndVisible() {
|
|
||||||
let uiwindowClass = UIWindow.self
|
|
||||||
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
|
|
||||||
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
|
|
||||||
method_exchangeImplementations(m1, m2)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -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 {}
|
Binary file not shown.
@ -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 {}
|
Binary file not shown.
@ -0,0 +1,6 @@
|
|||||||
|
framework module SoftwareKeyboard {
|
||||||
|
umbrella header "SoftwareKeyboard.h"
|
||||||
|
export *
|
||||||
|
|
||||||
|
module * { export * }
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
BIN
src/MeloNX/MeloNX/Dependencies/Ryujinx.Headless.SDL2.dylib
Normal file
BIN
src/MeloNX/MeloNX/Dependencies/Ryujinx.Headless.SDL2.dylib
Normal file
Binary file not shown.
Binary file not shown.
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>MeloID</key>
|
||||||
|
<string></string>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
|
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -6,12 +6,118 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct MeloNXApp: App {
|
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 {
|
var body: some Scene {
|
||||||
WindowGroup {
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
45
src/MeloNX/MeloNX/Models/Game.swift
Normal file
45
src/MeloNX/MeloNX/Models/Game.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,245 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SDL2
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
struct MoltenVKSettings: Codable, Hashable {
|
|
||||||
let string: String
|
|
||||||
var bool: Bool?
|
|
||||||
var value: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
// MARK: - Properties
|
|
||||||
@State private var theWindow: UIWindow?
|
|
||||||
@State private var virtualController: GCVirtualController?
|
|
||||||
@State private var game: URL?
|
|
||||||
@State private var controllersList: [Controller] = []
|
|
||||||
@State private var currentControllers: [Controller] = []
|
|
||||||
@State private var config: Ryujinx.Configuration
|
|
||||||
@State private var settings: [MoltenVKSettings]
|
|
||||||
@State private var isVirtualControllerActive: Bool = false
|
|
||||||
|
|
||||||
// MARK: - Initialization
|
|
||||||
init() {
|
|
||||||
let defaultConfig = Ryujinx.Configuration(gamepath: "")
|
|
||||||
_config = State(initialValue: defaultConfig)
|
|
||||||
|
|
||||||
let defaultSettings: [MoltenVKSettings] = [
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"),
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1")
|
|
||||||
]
|
|
||||||
_settings = State(initialValue: defaultSettings)
|
|
||||||
|
|
||||||
initializeSDL()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Body
|
|
||||||
var body: some View {
|
|
||||||
iOSNav {
|
|
||||||
if let game {
|
|
||||||
emulationView
|
|
||||||
} else {
|
|
||||||
mainMenuView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: isVirtualControllerActive) { newValue in
|
|
||||||
if newValue {
|
|
||||||
createVirtualController()
|
|
||||||
} else {
|
|
||||||
destroyVirtualController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - View Components
|
|
||||||
private var emulationView: some View {
|
|
||||||
ZStack {}
|
|
||||||
.onAppear {
|
|
||||||
setupEmulation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mainMenuView: some View {
|
|
||||||
HStack {
|
|
||||||
GameListView(startemu: $game)
|
|
||||||
.onAppear {
|
|
||||||
createVirtualController()
|
|
||||||
refreshControllersList()
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsListView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var settingsListView: some View {
|
|
||||||
List {
|
|
||||||
Section("Settings") {
|
|
||||||
NavigationLink("Config") {
|
|
||||||
SettingsView(config: $config, MoltenVKSettings: $settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section("Controller") {
|
|
||||||
Button("Refresh", action: refreshControllersList)
|
|
||||||
|
|
||||||
ForEach(controllersList, id: \.self) { controller in
|
|
||||||
if controller.name != "Apple Touch Controller" {
|
|
||||||
controllerRow(for: controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func controllerRow(for controller: Controller) -> some View {
|
|
||||||
HStack {
|
|
||||||
Button(controller.name) {
|
|
||||||
toggleController(controller)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
if currentControllers.contains(where: { $0.id == controller.id }) {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Controller Management
|
|
||||||
private func createVirtualController() {
|
|
||||||
let configuration = GCVirtualController.Configuration()
|
|
||||||
configuration.elements = [
|
|
||||||
/*
|
|
||||||
GCInputLeftThumbstick,
|
|
||||||
GCInputRightThumbstick,
|
|
||||||
GCInputButtonA,
|
|
||||||
GCInputButtonB,
|
|
||||||
GCInputButtonX,
|
|
||||||
GCInputButtonY,
|
|
||||||
*/
|
|
||||||
]
|
|
||||||
|
|
||||||
virtualController = GCVirtualController(configuration: configuration)
|
|
||||||
virtualController?.connect()
|
|
||||||
|
|
||||||
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
}
|
|
||||||
|
|
||||||
private func destroyVirtualController() {
|
|
||||||
virtualController?.disconnect()
|
|
||||||
virtualController = nil
|
|
||||||
|
|
||||||
// Remove virtual controller from current controllers
|
|
||||||
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
|
||||||
private func initializeSDL() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
setMoltenVKSettings()
|
|
||||||
SDL_SetMainReady()
|
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE)
|
|
||||||
SDL_Init(SDL_INIT_VIDEO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupEmulation() {
|
|
||||||
virtualController?.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
controllerCallback = {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
controllersList = Ryujinx.shared.getConnectedControllers()
|
|
||||||
currentControllers.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
if controllersList.count == 2,
|
|
||||||
controllersList.contains(where: { $0.name == "Apple Touch Controller" }) {
|
|
||||||
currentControllers.append(controllersList[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
print(currentControllers)
|
|
||||||
start(displayid: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
showVirtualController()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func refreshControllersList() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
|
||||||
controllersList = Ryujinx.shared.getConnectedControllers()
|
|
||||||
controllersList.removeAll(where: { $0.id == "0" })
|
|
||||||
|
|
||||||
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
|
|
||||||
|
|
||||||
if let controller = controllersList.first, !controllersList.isEmpty {
|
|
||||||
currentControllers.append(controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func toggleController(_ controller: Controller) {
|
|
||||||
if currentControllers.contains(where: { $0.id == controller.id }) {
|
|
||||||
currentControllers.removeAll(where: { $0.id == controller.id })
|
|
||||||
} else {
|
|
||||||
currentControllers.append(controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func start(displayid: UInt32) {
|
|
||||||
guard let game else { return }
|
|
||||||
|
|
||||||
config.gamepath = game.path
|
|
||||||
config.inputids = currentControllers.map(\.id)
|
|
||||||
|
|
||||||
allocateMemory()
|
|
||||||
|
|
||||||
do {
|
|
||||||
try Ryujinx.shared.start(with: config)
|
|
||||||
} catch {
|
|
||||||
print("Error: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func allocateMemory() {
|
|
||||||
let physicalMemory = ProcessInfo.processInfo.physicalMemory
|
|
||||||
let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024)
|
|
||||||
|
|
||||||
let pointer = UnsafeMutableRawPointer.allocate(
|
|
||||||
byteCount: Int(totalMemoryInGB),
|
|
||||||
alignment: MemoryLayout<UInt8>.alignment
|
|
||||||
)
|
|
||||||
pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(totalMemoryInGB))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setMoltenVKSettings() {
|
|
||||||
if let configs = loadSettings() {
|
|
||||||
self.config = configs
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.forEach { setting in
|
|
||||||
setenv(setting.string, setting.value, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Functions
|
|
||||||
func loadSettings() -> Ryujinx.Configuration? {
|
|
||||||
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
|
|
||||||
let data = jsonString.data(using: .utf8) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
|
|
||||||
} catch {
|
|
||||||
print("Failed to load settings: \(error)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
//
|
|
||||||
// GameListView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
// MARK: - This will most likely not be used in prod
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct GameListView: View {
|
|
||||||
@Binding var startemu: URL?
|
|
||||||
@State private var games: [URL] = []
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List(games, id: \.self) { game in
|
|
||||||
Button {
|
|
||||||
startemu = game
|
|
||||||
} label: {
|
|
||||||
Text(game.lastPathComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Games")
|
|
||||||
.onAppear(perform: loadGames)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadGames() {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
|
||||||
|
|
||||||
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
|
||||||
|
|
||||||
// Check if "roms" folder exists; if not, create it
|
|
||||||
if !fileManager.fileExists(atPath: romsDirectory.path) {
|
|
||||||
do {
|
|
||||||
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
} catch {
|
|
||||||
print("Failed to create roms directory: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load games only from "roms" folder
|
|
||||||
do {
|
|
||||||
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
|
|
||||||
games = files
|
|
||||||
} catch {
|
|
||||||
print("Error loading games from roms folder: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// VulkanSDLView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import MetalKit
|
|
||||||
import SDL2
|
|
||||||
|
|
||||||
class SDLView: UIView {
|
|
||||||
var sdlwin: OpaquePointer?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
makeSDLWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
makeSDLWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWindowFlags() -> UInt32 {
|
|
||||||
return SDL_WINDOW_VULKAN.rawValue
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeSDLWindow() {
|
|
||||||
let width: Int32 = 1280 // Replace with the desired width
|
|
||||||
let height: Int32 = 720 // Replace with the desired height
|
|
||||||
|
|
||||||
let defaultFlags: UInt32 = SDL_WINDOW_SHOWN.rawValue
|
|
||||||
let fullscreenFlag: UInt32 = SDL_WINDOW_FULLSCREEN.rawValue // Or SDL_WINDOW_FULLSCREEN_DESKTOP if needed
|
|
||||||
|
|
||||||
// Create the SDL window
|
|
||||||
sdlwin = SDL_CreateWindow(
|
|
||||||
"Ryujinx",
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
defaultFlags | getWindowFlags() // | fullscreenFlag | getWindowFlags()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if we successfully retrieved the SDL window
|
|
||||||
guard sdlwin != nil else {
|
|
||||||
print("Error creating SDL window: \(String(cString: SDL_GetError()))")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
print("SDL window created successfully.")
|
|
||||||
|
|
||||||
// Position SDL window over this UIView
|
|
||||||
self.syncSDLWindowPosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func syncSDLWindowPosition() {
|
|
||||||
guard let sdlwin = sdlwin else { return }
|
|
||||||
|
|
||||||
|
|
||||||
// Get the frame of the UIView in screen coordinates
|
|
||||||
let viewFrameInWindow = self.convert(self.bounds, to: nil)
|
|
||||||
|
|
||||||
// Set the SDL window position and size to match the UIView frame
|
|
||||||
SDL_SetWindowPosition(sdlwin, Int32(viewFrameInWindow.origin.x), Int32(viewFrameInWindow.origin.y))
|
|
||||||
SDL_SetWindowSize(sdlwin, Int32(viewFrameInWindow.width), Int32(viewFrameInWindow.height))
|
|
||||||
|
|
||||||
// Bring SDL window to the front
|
|
||||||
SDL_RaiseWindow(sdlwin)
|
|
||||||
|
|
||||||
print("SDL window positioned over SDLView.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
// Adjust SDL window whenever layout changes
|
|
||||||
syncSDLWindowPosition()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// VulkanSDLViewRepresentable.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import SwiftUI
|
|
||||||
import SDL2
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
struct SDLViewRepresentable: UIViewRepresentable {
|
|
||||||
let configure: (UInt32) -> Void
|
|
||||||
func makeUIView(context: Context) -> SDLView {
|
|
||||||
// Configure (start ryu) before initialsing SDLView so SDLView can get the SDL_Window from Ryu
|
|
||||||
let view = SDLView(frame: .zero)
|
|
||||||
configure(SDL_GetWindowID(view.sdlwin))
|
|
||||||
return view
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: SDLView, context: Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
//
|
|
||||||
// SettingsView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 25/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SettingsView: View {
|
|
||||||
@Binding var config: Ryujinx.Configuration
|
|
||||||
@Binding var MoltenVKSettings: [MoltenVKSettings]
|
|
||||||
|
|
||||||
var memoryManagerModes = [
|
|
||||||
("HostMapped", "Host (fast)"),
|
|
||||||
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
|
|
||||||
("SoftwarePageTable", "Software (slow)"),
|
|
||||||
]
|
|
||||||
|
|
||||||
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
|
|
||||||
|
|
||||||
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView {
|
|
||||||
VStack {
|
|
||||||
Section(header: Title("Graphics and Performance")) {
|
|
||||||
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
|
|
||||||
Toggle("Disable V-Sync", isOn: $config.disableVSync)
|
|
||||||
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
|
|
||||||
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
|
|
||||||
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
|
|
||||||
Resolution(value: $config.resscale)
|
|
||||||
Toggle("Enable Metal HUD", isOn: $metalHUDEnabled)
|
|
||||||
.onChange(of: metalHUDEnabled) { newValue in
|
|
||||||
if newValue {
|
|
||||||
MTLHud.shared.enable()
|
|
||||||
} else {
|
|
||||||
MTLHud.shared.disable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Title("Input Settings")) {
|
|
||||||
Toggle("List Input IDs", isOn: $config.listinputids)
|
|
||||||
Toggle("Nintendo Controller Layout", isOn: $config.nintendoinput)
|
|
||||||
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo)
|
|
||||||
// Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory)
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Title("Logging Settings")) {
|
|
||||||
Toggle("Enable Debug Logs", isOn: $config.debuglogs)
|
|
||||||
Toggle("Enable Trace Logs", isOn: $config.tracelogs)
|
|
||||||
}
|
|
||||||
Section(header: Title("CPU Mode")) {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
Picker("Memory Manager Mode", selection: $config.memoryManagerMode) {
|
|
||||||
ForEach(memoryManagerModes, id: \.0) { key, displayName in
|
|
||||||
Text(displayName).tag(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(MenuPickerStyle()) // Dropdown style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Section(header: Title("Additional Settings")) {
|
|
||||||
//TextField("Game Path", text: $config.gamepath)
|
|
||||||
|
|
||||||
Text("PageSize \(String(Int(getpagesize())))")
|
|
||||||
|
|
||||||
TextField("Additional Arguments", text: Binding(
|
|
||||||
get: {
|
|
||||||
config.additionalArgs.joined(separator: ", ")
|
|
||||||
},
|
|
||||||
set: { newValue in
|
|
||||||
config.additionalArgs = newValue.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
if let configs = loadSettings() {
|
|
||||||
self.config = configs
|
|
||||||
print(configs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("Settings")
|
|
||||||
.navigationBarItems(trailing: Button("Save") {
|
|
||||||
saveSettings()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveSettings() {
|
|
||||||
do {
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
encoder.outputFormatting = .prettyPrinted // Optional: Makes the JSON easier to read
|
|
||||||
let data = try encoder.encode(config)
|
|
||||||
let jsonString = String(data: data, encoding: .utf8)
|
|
||||||
|
|
||||||
// Save to UserDefaults
|
|
||||||
UserDefaults.standard.set(jsonString, forKey: "config")
|
|
||||||
|
|
||||||
print("Settings saved successfully!")
|
|
||||||
} catch {
|
|
||||||
print("Failed to save settings: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Resolution: View {
|
|
||||||
@Binding var value: Float
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Text("Resolution Scale (Custom):")
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
if value > 0.1 { // Prevent values going below 0.1
|
|
||||||
value -= 0.10
|
|
||||||
value = round(value * 1000) / 1000 // Round to two decimal places
|
|
||||||
}
|
|
||||||
print(value)
|
|
||||||
}) {
|
|
||||||
Text("-")
|
|
||||||
.frame(width: 30, height: 30)
|
|
||||||
.background(Color.gray.opacity(0.2))
|
|
||||||
.cornerRadius(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField("", value: $value, formatter: NumberFormatter.floatFormatter)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.frame(width: 60)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
.keyboardType(.decimalPad)
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
value += 0.10
|
|
||||||
value = round(value * 1000) / 1000 // Round to two decimal places
|
|
||||||
print(value)
|
|
||||||
}) {
|
|
||||||
Text("+")
|
|
||||||
.frame(width: 30, height: 30)
|
|
||||||
.background(Color.gray.opacity(0.2))
|
|
||||||
.cornerRadius(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NumberFormatter {
|
|
||||||
static var floatFormatter: NumberFormatter {
|
|
||||||
let formatter = NumberFormatter()
|
|
||||||
formatter.numberStyle = .decimal
|
|
||||||
formatter.maximumFractionDigits = 2
|
|
||||||
formatter.minimumFractionDigits = 2
|
|
||||||
formatter.allowsFloats = true
|
|
||||||
return formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Title: View {
|
|
||||||
let string: String
|
|
||||||
|
|
||||||
init(_ string: String) {
|
|
||||||
self.string = string
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Text(string)
|
|
||||||
.font(.title2)
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
11
src/MeloNX/dotnet.xcconfig.example
Normal file
11
src/MeloNX/dotnet.xcconfig.example
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// dotnet.xcconfig
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by June P on 12/25/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Configuration settings file format documentation can be found at:
|
||||||
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
|
DOTNET_PATH = $(HOME)/.dotnet/dotnet
|
@ -51,6 +51,59 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using ARMeilleure.Translation;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using Ryujinx.Audio.Backends.Dummy;
|
||||||
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
|
using Ryujinx.Audio.Backends.SoundIo;
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Ava.Common;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Input;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Ava.UI.Renderer;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ava.UI.Windows;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Configuration.Multiplayer;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.SystemInterop;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Ryujinx.HLE;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
|
using Ryujinx.Input;
|
||||||
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using Ryujinx.Ui.Common;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using SPB.Graphics.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
@ -63,8 +63,16 @@ namespace Ryujinx.Common.Configuration
|
|||||||
{
|
{
|
||||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
}
|
}
|
||||||
|
string userProfilePath;
|
||||||
|
if (OperatingSystem.IsIOS())
|
||||||
|
{
|
||||||
|
userProfilePath = appDataPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
||||||
|
}
|
||||||
|
|
||||||
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
|
||||||
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
||||||
|
|
||||||
if (Directory.Exists(portablePath))
|
if (Directory.Exists(portablePath))
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_textures.AsSpan().Fill(initialImageInfo);
|
_textures.AsSpan().Fill(initialImageInfo);
|
||||||
_images.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.
|
// If null descriptors are supported, we can pass null as the handle.
|
||||||
_dummyBuffer = null;
|
_dummyBuffer = null;
|
||||||
|
@ -103,13 +103,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SupportsShaderStencilExport = supportsShaderStencilExport;
|
SupportsShaderStencilExport = supportsShaderStencilExport;
|
||||||
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
||||||
SupportsConditionalRendering = supportsConditionalRendering;
|
SupportsConditionalRendering = supportsConditionalRendering;
|
||||||
if (OperatingSystem.IsIOS()) {
|
SupportsExtendedDynamicState = (OperatingSystem.IsIOS() ? OperatingSystem.IsIOSVersionAtLeast(17) ? supportsExtendedDynamicState : false : supportsExtendedDynamicState);
|
||||||
SupportsExtendedDynamicState = (OperatingSystem.IsOSPlatformVersionAtLeast("iOS", 17) ? supportsExtendedDynamicState : false);
|
|
||||||
} else {
|
|
||||||
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
|
||||||
}
|
|
||||||
SupportsMultiView = supportsMultiView;
|
SupportsMultiView = supportsMultiView;
|
||||||
SupportsNullDescriptors = (OperatingSystem.IsIOS() ? false : supportsNullDescriptors);
|
SupportsNullDescriptors = supportsNullDescriptors;
|
||||||
SupportsPushDescriptors = supportsPushDescriptors;
|
SupportsPushDescriptors = supportsPushDescriptors;
|
||||||
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
|
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
|
||||||
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
|
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
|
||||||
|
@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
|||||||
config.UseMetalArgumentBuffers = true;
|
config.UseMetalArgumentBuffers = true;
|
||||||
|
|
||||||
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
||||||
config.SynchronousQueueSubmits = false;
|
// config.SynchronousQueueSubmits = false;
|
||||||
|
|
||||||
config.ResumeLostDevice = true;
|
config.ResumeLostDevice = true;
|
||||||
|
|
||||||
|
@ -301,6 +301,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
properties.Limits.FramebufferDepthSampleCounts &
|
properties.Limits.FramebufferDepthSampleCounts &
|
||||||
properties.Limits.FramebufferStencilSampleCounts;
|
properties.Limits.FramebufferStencilSampleCounts;
|
||||||
|
|
||||||
|
bool isDynamicStateSupported = OperatingSystem.IsIOS()
|
||||||
|
? OperatingSystem.IsIOSVersionAtLeast(17) && _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName)
|
||||||
|
: _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName);
|
||||||
|
|
||||||
Capabilities = new HardwareCapabilities(
|
Capabilities = new HardwareCapabilities(
|
||||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
|
||||||
supportsCustomBorderColor,
|
supportsCustomBorderColor,
|
||||||
@ -316,9 +320,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
||||||
features2.Features.ShaderStorageImageMultisample,
|
features2.Features.ShaderStorageImageMultisample,
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
||||||
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
isDynamicStateSupported,
|
||||||
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
|
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
|
||||||
featuresRobustness2.NullDescriptor || !IsMoltenVk,
|
!IsMoltenVk ? featuresRobustness2.NullDescriptor : false,
|
||||||
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
||||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
||||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
|
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
|
||||||
|
@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
{
|
{
|
||||||
internal class SoftwareKeyboardApplet : IApplet
|
internal class SoftwareKeyboardApplet : IApplet
|
||||||
{
|
{
|
||||||
private const string DefaultInputText = "Ryujinx";
|
private const string DefaultInputText = "MeloNX";
|
||||||
|
|
||||||
private const int StandardBufferSize = 0x7D8;
|
private const int StandardBufferSize = 0x7D8;
|
||||||
private const int InteractiveBufferSize = 0x7D4;
|
private const int InteractiveBufferSize = 0x7D4;
|
||||||
@ -180,6 +180,10 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
return _keyboardRenderer?.DrawTo(destination, position) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DllImport("SoftwareKeyboard.framework/SoftwareKeyboard", EntryPoint = "displayInputDialog", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void DisplayInputDialog(ref SoftwareKeyboardUiArgs args, out IntPtr userInput);
|
||||||
|
|
||||||
|
|
||||||
private void ExecuteForegroundKeyboard()
|
private void ExecuteForegroundKeyboard()
|
||||||
{
|
{
|
||||||
string initialText = null;
|
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
|
// InitialStringOffset points to the memory offset and InitialStringLength is the number of UTF-16 characters
|
||||||
if (_transferMemory != null && _keyboardForegroundConfig.InitialStringLength > 0)
|
if (_transferMemory != null && _keyboardForegroundConfig.InitialStringLength > 0)
|
||||||
{
|
{
|
||||||
initialText = Encoding.Unicode.GetString(_transferMemory, _keyboardForegroundConfig.InitialStringOffset,
|
initialText = Encoding.Unicode.GetString(
|
||||||
|
_transferMemory,
|
||||||
|
_keyboardForegroundConfig.InitialStringOffset,
|
||||||
2 * _keyboardForegroundConfig.InitialStringLength);
|
2 * _keyboardForegroundConfig.InitialStringLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the max string length is 0, we set it to a large default
|
// If the max string length is 0, we set it to a large default length.
|
||||||
// length.
|
|
||||||
if (_keyboardForegroundConfig.StringLengthMax == 0)
|
if (_keyboardForegroundConfig.StringLengthMax == 0)
|
||||||
{
|
{
|
||||||
_keyboardForegroundConfig.StringLengthMax = 100;
|
_keyboardForegroundConfig.StringLengthMax = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no GUI handler is set, fallback to default behavior.
|
||||||
if (_device.UiHandler == null)
|
if (_device.UiHandler == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default");
|
Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default");
|
||||||
|
|
||||||
_textValue = DefaultInputText;
|
// Prepare the SoftwareKeyboardUiArgs struct
|
||||||
_lastResult = KeyboardResult.Accept;
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -226,46 +264,36 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
_textValue ??= initialText ?? DefaultInputText;
|
_textValue ??= initialText ?? DefaultInputText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the game requests a string with a minimum length less
|
// Ensure the text meets the minimum length requirement
|
||||||
// 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.
|
|
||||||
while (_textValue.Length < _keyboardForegroundConfig.StringLengthMin)
|
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,
|
// Truncate the text if it exceeds the maximum length
|
||||||
// we truncate it.
|
|
||||||
if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax)
|
if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax)
|
||||||
{
|
{
|
||||||
_textValue = _textValue[.._keyboardForegroundConfig.StringLengthMax];
|
_textValue = _textValue[.._keyboardForegroundConfig.StringLengthMax];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does the application want to validate the text itself?
|
// Handle text validation if required
|
||||||
if (_keyboardForegroundConfig.CheckText)
|
if (_keyboardForegroundConfig.CheckText)
|
||||||
{
|
{
|
||||||
// The application needs to validate the response, so we
|
// Submit text for validation
|
||||||
// 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.
|
|
||||||
_foregroundState = SoftwareKeyboardState.ValidationPending;
|
_foregroundState = SoftwareKeyboardState.ValidationPending;
|
||||||
|
|
||||||
PushForegroundResponse(true);
|
PushForegroundResponse(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If the application doesn't need to validate the response,
|
// Submit text as complete
|
||||||
// we push the data to the non-interactive output buffer
|
|
||||||
// and poll it for completion.
|
|
||||||
_foregroundState = SoftwareKeyboardState.Complete;
|
_foregroundState = SoftwareKeyboardState.Complete;
|
||||||
|
|
||||||
PushForegroundResponse(false);
|
PushForegroundResponse(false);
|
||||||
|
|
||||||
AppletStateChanged?.Invoke(this, null);
|
AppletStateChanged?.Invoke(this, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void OnInteractiveData(object sender, EventArgs e)
|
private void OnInteractiveData(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// Obtain the validation status response.
|
// Obtain the validation status response.
|
||||||
|
@ -116,10 +116,10 @@ private void CreateFonts(string uiThemeFontFamily)
|
|||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
availableFonts = new string[] {
|
availableFonts = new string[] {
|
||||||
"Chalkboard",
|
"SF Pro",
|
||||||
"Chalkboard", // San Francisco is the default font on iOS
|
"New York",
|
||||||
"Chalkboard", // Legacy iOS font
|
"Helvetica Neue",
|
||||||
"Chalkboard" // Common system font
|
"Avenir"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -40,7 +40,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
{
|
{
|
||||||
byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
|
byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
|
||||||
|
|
||||||
AddUser("RyuPlayer", defaultUserImage, DefaultUserId);
|
AddUser("MeloNX", defaultUserImage, DefaultUserId);
|
||||||
|
|
||||||
OpenUser(DefaultUserId);
|
OpenUser(DefaultUserId);
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 16 KiB |
@ -31,7 +31,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
// Input
|
// 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; }
|
public bool OnScreenCorrespond { get; set; }
|
||||||
|
|
||||||
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
|
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
|
||||||
|
@ -29,6 +29,9 @@ using Ryujinx.Input;
|
|||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using Ryujinx.Input.SDL2;
|
using Ryujinx.Input.SDL2;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Configuration.System;
|
||||||
|
using Ryujinx.Ui.Common;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
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 ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||||
using System.Linq;
|
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
|
public class GamepadInfo
|
||||||
{
|
{
|
||||||
@ -93,6 +147,57 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return 0;
|
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)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
// 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")]
|
[UnmanagedCallersOnly(EntryPoint = "get_game_controllers")]
|
||||||
public static unsafe IntPtr GetGamepadList()
|
public static unsafe IntPtr GetGamepadList()
|
||||||
{
|
{
|
||||||
List<GamepadInfo> gamepads = new List<GamepadInfo>();
|
List<GamepadInfo> gamepads = new List<GamepadInfo>();
|
||||||
IGamepad gamepad;
|
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
|
// Collect gamepads from the keyboard driver
|
||||||
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
|
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
|
||||||
@ -170,6 +355,466 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return ptr;
|
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)
|
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
|
||||||
{
|
{
|
||||||
if (inputId == null)
|
if (inputId == null)
|
||||||
@ -204,7 +849,11 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
|
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)
|
static void Load(Options option)
|
||||||
{
|
{
|
||||||
AppDataManager.Initialize(option.BaseDataDir);
|
|
||||||
|
|
||||||
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
if (_virtualFileSystem == null)
|
||||||
_libHacHorizonManager = new LibHacHorizonManager();
|
{
|
||||||
|
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
||||||
|
}
|
||||||
|
|
||||||
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
|
if (_libHacHorizonManager == null)
|
||||||
_libHacHorizonManager.InitializeArpServer();
|
{
|
||||||
_libHacHorizonManager.InitializeBcatServer();
|
_libHacHorizonManager = new LibHacHorizonManager();
|
||||||
_libHacHorizonManager.InitializeSystemClients();
|
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
|
||||||
|
_libHacHorizonManager.InitializeArpServer();
|
||||||
|
_libHacHorizonManager.InitializeBcatServer();
|
||||||
|
_libHacHorizonManager.InitializeSystemClients();
|
||||||
|
}
|
||||||
|
|
||||||
_contentManager = new ContentManager(_virtualFileSystem);
|
if (_contentManager == null)
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
|
{
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_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;
|
GraphicsConfig.EnableShaderCache = true;
|
||||||
|
|
||||||
@ -464,6 +1132,12 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (option.InputPath == "MiiMaker") {
|
||||||
|
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
|
option.InputPath = contentPath;
|
||||||
|
}
|
||||||
|
|
||||||
_inputConfiguration = new List<InputConfig>();
|
_inputConfiguration = new List<InputConfig>();
|
||||||
_enableKeyboard = option.EnableKeyboard;
|
_enableKeyboard = option.EnableKeyboard;
|
||||||
_enableMouse = option.EnableMouse;
|
_enableMouse = option.EnableMouse;
|
||||||
@ -632,7 +1306,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
options.IgnoreMissingServices,
|
options.IgnoreMissingServices,
|
||||||
options.AspectRatio,
|
options.AspectRatio,
|
||||||
options.AudioVolume,
|
options.AudioVolume,
|
||||||
options.UseHypervisor ?? true,
|
options.UseHypervisor ?? false,
|
||||||
options.MultiplayerLanInterfaceId,
|
options.MultiplayerLanInterfaceId,
|
||||||
Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm);
|
Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm);
|
||||||
|
|
||||||
@ -688,6 +1362,17 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
|
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))
|
if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
|
string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
|
||||||
@ -756,23 +1441,35 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
if (isFirmwareTitle) {
|
||||||
try
|
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
||||||
{
|
|
||||||
if (!_emulationContext.LoadProgram(path))
|
if (!_emulationContext.LoadNca(path))
|
||||||
{
|
{
|
||||||
_emulationContext.Dispose();
|
_emulationContext.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
else {
|
||||||
{
|
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
||||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@ -794,5 +1491,83 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,13 @@
|
|||||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||||
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
|
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
|
||||||
<TieredPGO>true</TieredPGO>
|
<TieredPGO>true</TieredPGO>
|
||||||
|
|
||||||
<PublishAot>true</PublishAot>
|
<PublishAot>true</PublishAot>
|
||||||
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
|
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
|
||||||
|
<AotCompilerOptions>-flto -Ofast -funroll-loops</AotCompilerOptions>
|
||||||
<UseNativeAOTRuntime>true</UseNativeAOTRuntime>
|
<UseNativeAOTRuntime>true</UseNativeAOTRuntime>
|
||||||
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
||||||
|
<OptimizationPreference>Speed</OptimizationPreference>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- iOS linking stuff from godot -->
|
<!-- iOS linking stuff from godot -->
|
||||||
@ -46,7 +48,8 @@
|
|||||||
<Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
|
<Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
|
||||||
|
|
||||||
<ItemGroup>
|
<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"
|
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
|
||||||
Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
|
Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
|
||||||
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
|
<LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
|
||||||
@ -80,7 +83,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="OpenTK.Core" />
|
<PackageReference Include="OpenTK.Core" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
|
<!-- <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> -->
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="false">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="false">
|
||||||
@ -97,6 +100,8 @@
|
|||||||
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -45,14 +45,14 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
_mainThreadActions.Enqueue(action);
|
_mainThreadActions.Enqueue(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NpadManager NpadManager { get; }
|
public NpadManager NpadManager;
|
||||||
public TouchScreenManager TouchScreenManager { get; }
|
public TouchScreenManager TouchScreenManager;
|
||||||
public Switch Device { get; private set; }
|
public Switch Device;
|
||||||
public IRenderer Renderer { get; private set; }
|
public IRenderer Renderer;
|
||||||
|
|
||||||
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||||
|
|
||||||
protected IntPtr WindowHandle { get; set; }
|
public IntPtr WindowHandle;
|
||||||
|
|
||||||
public IHostUiTheme HostUiTheme { get; }
|
public IHostUiTheme HostUiTheme { get; }
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
@ -66,18 +66,18 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
public ScalingFilter ScalingFilter { get; set; }
|
public ScalingFilter ScalingFilter { get; set; }
|
||||||
public int ScalingFilterLevel { get; set; }
|
public int ScalingFilterLevel { get; set; }
|
||||||
|
|
||||||
protected SDL2MouseDriver MouseDriver;
|
public SDL2MouseDriver MouseDriver;
|
||||||
private readonly InputManager _inputManager;
|
private readonly InputManager _inputManager;
|
||||||
private readonly IKeyboard _keyboardInterface;
|
private readonly IKeyboard _keyboardInterface;
|
||||||
private readonly GraphicsDebugLevel _glLogLevel;
|
private readonly GraphicsDebugLevel _glLogLevel;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
||||||
private readonly ManualResetEvent _exitEvent;
|
public ManualResetEvent _exitEvent;
|
||||||
private readonly ManualResetEvent _gpuDoneEvent;
|
public ManualResetEvent _gpuDoneEvent;
|
||||||
|
|
||||||
private long _ticks;
|
private long _ticks;
|
||||||
private bool _isActive;
|
public bool _isActive;
|
||||||
private bool _isStopped;
|
private bool _isStopped;
|
||||||
private uint _windowId;
|
private uint _windowId;
|
||||||
|
|
||||||
|
@ -28,141 +28,187 @@ namespace Ryujinx.Memory
|
|||||||
[LibraryImport("libc")]
|
[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);
|
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;
|
private static class Flags
|
||||||
const int MAP_MEM_NAMED_CREATE = 0x020000;
|
{
|
||||||
|
public const int MAP_MEM_LEDGER_TAGGED = 0x002000;
|
||||||
const int VM_PROT_READ = 0x01;
|
public const int MAP_MEM_NAMED_CREATE = 0x020000;
|
||||||
const int VM_PROT_WRITE = 0x02;
|
public const int VM_PROT_READ = 0x01;
|
||||||
const int VM_PROT_EXECUTE = 0x04;
|
public const int VM_PROT_WRITE = 0x02;
|
||||||
|
public const int VM_PROT_EXECUTE = 0x04;
|
||||||
const int VM_LEDGER_TAG_DEFAULT = 0x00000001;
|
public const int VM_LEDGER_TAG_DEFAULT = 0x00000001;
|
||||||
const int VM_LEDGER_FLAG_NO_FOOTPRINT = 0x00000001;
|
public const int VM_LEDGER_FLAG_NO_FOOTPRINT = 0x00000001;
|
||||||
|
public const int VM_INHERIT_COPY = 1;
|
||||||
const int VM_INHERIT_COPY = 1;
|
public const int VM_INHERIT_DEFAULT = VM_INHERIT_COPY;
|
||||||
const int VM_INHERIT_DEFAULT = VM_INHERIT_COPY;
|
public const int VM_FLAGS_FIXED = 0x0000;
|
||||||
|
public const int VM_FLAGS_ANYWHERE = 0x0001;
|
||||||
const int VM_FLAGS_FIXED = 0x0000;
|
public const int VM_FLAGS_OVERWRITE = 0x4000;
|
||||||
const int VM_FLAGS_ANYWHERE = 0x0001;
|
}
|
||||||
const int VM_FLAGS_OVERWRITE = 0x4000;
|
|
||||||
|
private const IntPtr TASK_NULL = 0;
|
||||||
const IntPtr TASK_NULL = 0;
|
private static readonly IntPtr _selfTask;
|
||||||
|
private static readonly int DEFAULT_CHUNK_SIZE = 16 * 1024 * 1024;
|
||||||
public static void ReallocateBlock(IntPtr address, int size)
|
|
||||||
|
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 memorySize = (IntPtr)size;
|
||||||
IntPtr memoryObjectPort = IntPtr.Zero;
|
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
|
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)
|
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);
|
// Set ownership
|
||||||
|
HandleMachError(
|
||||||
if (err != 0)
|
mach_memory_entry_ownership(
|
||||||
{
|
memoryObjectPort,
|
||||||
throw new InvalidOperationException($"Failed to set ownership: {err}");
|
TASK_NULL,
|
||||||
}
|
Flags.VM_LEDGER_TAG_DEFAULT,
|
||||||
|
Flags.VM_LEDGER_FLAG_NO_FOOTPRINT),
|
||||||
|
"memory_entry_ownership");
|
||||||
|
|
||||||
IntPtr mapAddress = address;
|
IntPtr mapAddress = address;
|
||||||
|
|
||||||
err = vm_map(
|
// Map memory
|
||||||
selfTask,
|
HandleMachError(
|
||||||
&mapAddress,
|
vm_map(
|
||||||
memorySize,
|
_selfTask,
|
||||||
/*mask=*/ 0,
|
&mapAddress,
|
||||||
/*flags=*/ VM_FLAGS_OVERWRITE,
|
memorySize,
|
||||||
memoryObjectPort,
|
IntPtr.Zero,
|
||||||
/*offset=*/ 0,
|
Flags.VM_FLAGS_OVERWRITE,
|
||||||
/*copy=*/ 0,
|
memoryObjectPort,
|
||||||
VM_PROT_READ | VM_PROT_WRITE,
|
IntPtr.Zero,
|
||||||
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
|
0,
|
||||||
VM_INHERIT_COPY);
|
Flags.VM_PROT_READ | Flags.VM_PROT_WRITE,
|
||||||
|
Flags.VM_PROT_READ | Flags.VM_PROT_WRITE | Flags.VM_PROT_EXECUTE,
|
||||||
if (err != 0)
|
Flags.VM_INHERIT_COPY),
|
||||||
{
|
"vm_map");
|
||||||
throw new InvalidOperationException($"Failed to map: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (address != mapAddress)
|
if (address != mapAddress)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Remap changed address");
|
throw new InvalidOperationException("Memory mapping address mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return mapAddress;
|
||||||
}
|
}
|
||||||
finally
|
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)
|
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 endAddress = address + size;
|
||||||
IntPtr blockAddress = address;
|
|
||||||
while (blockAddress < endAddress)
|
while (currentAddress < endAddress)
|
||||||
{
|
{
|
||||||
int blockSize = Math.Min(mapChunkSize, (int)(endAddress - blockAddress));
|
int blockSize = Math.Min(chunkSize, (int)(endAddress - currentAddress));
|
||||||
|
ReallocateBlock(currentAddress, blockSize);
|
||||||
ReallocateBlock(blockAddress, blockSize);
|
currentAddress += blockSize;
|
||||||
|
|
||||||
blockAddress += blockSize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntPtr AllocateSharedMemory(ulong size, bool reserve)
|
public static IntPtr AllocateSharedMemory(ulong size, bool reserve)
|
||||||
{
|
{
|
||||||
IntPtr address = 0;
|
IntPtr address = IntPtr.Zero;
|
||||||
|
HandleMachError(
|
||||||
int err = vm_allocate(mach_task_self(), &address, (IntPtr)size, VM_FLAGS_ANYWHERE);
|
vm_allocate(_selfTask, &address, (IntPtr)size, Flags.VM_FLAGS_ANYWHERE),
|
||||||
|
"vm_allocate");
|
||||||
if (err != 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Failed to allocate shared memory: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DestroySharedMemory(IntPtr handle, ulong size)
|
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)
|
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 srcAddress = (IntPtr)((ulong)sharedMemory + srcOffset);
|
||||||
IntPtr dstAddress = location;
|
IntPtr dstAddress = location;
|
||||||
|
int curProtection = 0;
|
||||||
|
int maxProtection = 0;
|
||||||
|
|
||||||
int cur_protection = 0;
|
HandleMachError(
|
||||||
int max_protection = 0;
|
vm_remap(
|
||||||
|
_selfTask,
|
||||||
int err = vm_remap(taskSelf, &dstAddress, (IntPtr)size, 0, VM_FLAGS_OVERWRITE, taskSelf, srcAddress, 0, &cur_protection, &max_protection, VM_INHERIT_DEFAULT);
|
&dstAddress,
|
||||||
|
(IntPtr)size,
|
||||||
if (err != 0)
|
IntPtr.Zero,
|
||||||
{
|
Flags.VM_FLAGS_OVERWRITE,
|
||||||
throw new InvalidOperationException($"Failed to allocate remap memory: {err}");
|
_selfTask,
|
||||||
}
|
srcAddress,
|
||||||
|
0,
|
||||||
|
&curProtection,
|
||||||
|
&maxProtection,
|
||||||
|
Flags.VM_INHERIT_DEFAULT),
|
||||||
|
"vm_remap");
|
||||||
|
|
||||||
return dstAddress;
|
return dstAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UnmapView(IntPtr location, ulong size)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,8 +37,8 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
{
|
{
|
||||||
int pageBits = (int)ulong.Log2(Size);
|
int pageBits = (int)ulong.Log2(Size);
|
||||||
|
|
||||||
_ram = new MemoryBlock(Size * 2);
|
_ram = new MemoryBlock(Size);
|
||||||
_memory = new MemoryManager(_ram, 1ul << (pageBits + 4));
|
_memory = new MemoryManager(_ram, 1ul << (pageBits + 2));
|
||||||
_memory.IncrementReferenceCount();
|
_memory.IncrementReferenceCount();
|
||||||
|
|
||||||
// Some tests depends on hardcoded address that were computed for 4KiB.
|
// Some tests depends on hardcoded address that were computed for 4KiB.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user