forked from MeloNX/MeloNX
Compare commits
101 Commits
xc-ios-ht-
...
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 | ||
|
c419381e63 | ||
|
9a86b2000a | ||
|
b2424a9652 | ||
|
1d70417281 | ||
|
95da853b7f | ||
|
a14aadf878 | ||
|
45e2785e93 | ||
|
d3031752be | ||
|
c4c71a4cb6 | ||
|
8e229fe454 | ||
|
e02927cadb | ||
|
098467f3f3 | ||
|
8ebde3f921 | ||
|
ab28f9a24a | ||
|
674824184a | ||
1bc0d3dba9 | |||
|
0a3b4f71a9 | ||
|
b9163f9fde | ||
|
05d1730c17 | ||
|
e170ed01ad | ||
|
8d4f004a59 | ||
|
437f7f8c04 | ||
2ba59b2ce9 | |||
|
d9099429f2 | ||
|
bea7528eb6 | ||
|
05880cc8a5 | ||
|
10e45533e1 | ||
|
b86e3301bb | ||
|
165bb0c5d2 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
|
|
||||||
|
dotnet.xcconfig
|
||||||
|
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
[Rr]elease/
|
[Rr]elease/
|
||||||
x64/
|
x64/
|
||||||
@ -173,4 +175,4 @@ PublishProfiles/
|
|||||||
|
|
||||||
# Glade backup files
|
# Glade backup files
|
||||||
*.glade~
|
*.glade~
|
||||||
MeloNX-XC/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib
|
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib
|
||||||
|
33
Compile.md
Normal file
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
|
||||||
|
```
|
@ -33,15 +33,15 @@
|
|||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.3" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||||
|
@ -1,499 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 77;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
4E87E2F32CCE315100F54325 /* SDL in Frameworks */ = {isa = PBXBuildFile; productRef = 4E87E2F22CCE315100F54325 /* SDL */; };
|
|
||||||
4E87E2F62CCE33B500F54325 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E87E2F52CCE33B500F54325 /* GameController.framework */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
|
||||||
4E87E2CA2CCE2D8E00F54325 /* Embed Frameworks */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 10;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
name = "Embed Frameworks";
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
4E87E28C2CCE2C1000F54325 /* MeloNX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeloNX.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
4E87E2F52CCE33B500F54325 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
|
||||||
4E87E3022CCE3E0700F54325 /* Exceptions for "MeloNX" folder in "MeloNX" target */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
|
||||||
membershipExceptions = (
|
|
||||||
Info.plist,
|
|
||||||
);
|
|
||||||
target = 4E87E28B2CCE2C1000F54325 /* MeloNX */;
|
|
||||||
};
|
|
||||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
|
||||||
4E87E2CB2CCE2D8E00F54325 /* Exceptions for "MeloNX" folder in "Embed Frameworks" phase from "MeloNX" target */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
|
|
||||||
attributesByRelativePath = {
|
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
|
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (CodeSignOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/System_Globalization_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/System_IO_Compression_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/System_IO_Ports_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/System_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/System_Net_Security_Native.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/System_Security_Cryptography_Native_Apple.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/clrjit.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
Dependencies/XCFrameworks/coreclr.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
|
|
||||||
};
|
|
||||||
buildPhase = 4E87E2CA2CCE2D8E00F54325 /* Embed Frameworks */;
|
|
||||||
membershipExceptions = (
|
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
|
||||||
Dependencies/XCFrameworks/clrjit.xcframework,
|
|
||||||
Dependencies/XCFrameworks/coreclr.xcframework,
|
|
||||||
Dependencies/XCFrameworks/MoltenVK.xcframework,
|
|
||||||
Dependencies/XCFrameworks/SDL2.xcframework,
|
|
||||||
Dependencies/XCFrameworks/System_Globalization_Native.xcframework,
|
|
||||||
Dependencies/XCFrameworks/System_IO_Compression_Native.xcframework,
|
|
||||||
Dependencies/XCFrameworks/System_IO_Ports_Native.xcframework,
|
|
||||||
Dependencies/XCFrameworks/System_Native.xcframework,
|
|
||||||
Dependencies/XCFrameworks/System_Net_Security_Native.xcframework,
|
|
||||||
Dependencies/XCFrameworks/System_Security_Cryptography_Native_Apple.xcframework,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
|
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
|
||||||
4E87E28E2CCE2C1000F54325 /* MeloNX */ = {
|
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
|
||||||
exceptions = (
|
|
||||||
4E87E3022CCE3E0700F54325 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
|
|
||||||
4E87E2CB2CCE2D8E00F54325 /* Exceptions for "MeloNX" folder in "Embed Frameworks" phase from "MeloNX" target */,
|
|
||||||
);
|
|
||||||
path = MeloNX;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
4E87E2892CCE2C1000F54325 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
4E87E2F62CCE33B500F54325 /* GameController.framework in Frameworks */,
|
|
||||||
4E87E2F32CCE315100F54325 /* SDL in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
4E87E2832CCE2C1000F54325 = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4E87E28E2CCE2C1000F54325 /* MeloNX */,
|
|
||||||
4E87E2F42CCE33B500F54325 /* Frameworks */,
|
|
||||||
4E87E28D2CCE2C1000F54325 /* Products */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4E87E28D2CCE2C1000F54325 /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4E87E28C2CCE2C1000F54325 /* MeloNX.app */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4E87E2F42CCE33B500F54325 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4E87E2F52CCE33B500F54325 /* GameController.framework */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
4E87E28B2CCE2C1000F54325 /* MeloNX */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = 4E87E29A2CCE2C1100F54325 /* Build configuration list for PBXNativeTarget "MeloNX" */;
|
|
||||||
buildPhases = (
|
|
||||||
4ECF0AD52CD0FB5B00A3820B /* ShellScript */,
|
|
||||||
4E87E2882CCE2C1000F54325 /* Sources */,
|
|
||||||
4E87E2892CCE2C1000F54325 /* Frameworks */,
|
|
||||||
4E87E28A2CCE2C1000F54325 /* Resources */,
|
|
||||||
4E87E2CA2CCE2D8E00F54325 /* Embed Frameworks */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
fileSystemSynchronizedGroups = (
|
|
||||||
4E87E28E2CCE2C1000F54325 /* MeloNX */,
|
|
||||||
);
|
|
||||||
name = MeloNX;
|
|
||||||
packageProductDependencies = (
|
|
||||||
4E87E2F22CCE315100F54325 /* SDL */,
|
|
||||||
);
|
|
||||||
productName = MeloNX;
|
|
||||||
productReference = 4E87E28C2CCE2C1000F54325 /* MeloNX.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
4E87E2842CCE2C1000F54325 /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
BuildIndependentTargetsInParallel = 1;
|
|
||||||
LastSwiftUpdateCheck = 1600;
|
|
||||||
LastUpgradeCheck = 1600;
|
|
||||||
TargetAttributes = {
|
|
||||||
4E87E28B2CCE2C1000F54325 = {
|
|
||||||
CreatedOnToolsVersion = 16.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = 4E87E2872CCE2C1000F54325 /* Build configuration list for PBXProject "MeloNX" */;
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
Base,
|
|
||||||
);
|
|
||||||
mainGroup = 4E87E2832CCE2C1000F54325;
|
|
||||||
minimizedProjectReferenceProxies = 1;
|
|
||||||
packageReferences = (
|
|
||||||
4E87E2F12CCE315100F54325 /* XCRemoteSwiftPackageReference "SwiftSDL2" */,
|
|
||||||
);
|
|
||||||
preferredProjectObjectVersion = 77;
|
|
||||||
productRefGroup = 4E87E28D2CCE2C1000F54325 /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
4E87E28B2CCE2C1000F54325 /* MeloNX */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
4E87E28A2CCE2C1000F54325 /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
|
||||||
4ECF0AD52CD0FB5B00A3820B /* ShellScript */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
alwaysOutOfDate = 1;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/bash;
|
|
||||||
shellScript = "
|
|
||||||
";
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
4E87E2882CCE2C1000F54325 /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
4E87E2982CCE2C1100F54325 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_TESTABILITY = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
4E87E2992CCE2C1100F54325 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
4E87E29B2CCE2C1100F54325 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
);
|
|
||||||
LIBRARY_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
);
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Header/MeloNX-Bridging-Header.h";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
4E87E29C2CCE2C1100F54325 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
);
|
|
||||||
LIBRARY_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
);
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/Header/MeloNX-Bridging-Header.h";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
4E87E2872CCE2C1000F54325 /* Build configuration list for PBXProject "MeloNX" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
4E87E2982CCE2C1100F54325 /* Debug */,
|
|
||||||
4E87E2992CCE2C1100F54325 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
4E87E29A2CCE2C1100F54325 /* Build configuration list for PBXNativeTarget "MeloNX" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
4E87E29B2CCE2C1100F54325 /* Debug */,
|
|
||||||
4E87E29C2CCE2C1100F54325 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
|
||||||
4E87E2F12CCE315100F54325 /* XCRemoteSwiftPackageReference "SwiftSDL2" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/ctreffs/SwiftSDL2";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 1.4.1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
|
||||||
4E87E2F22CCE315100F54325 /* SDL */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 4E87E2F12CCE315100F54325 /* XCRemoteSwiftPackageReference "SwiftSDL2" */;
|
|
||||||
productName = SDL;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
|
||||||
};
|
|
||||||
rootObject = 4E87E2842CCE2C1000F54325 /* Project object */;
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"originHash" : "188cbfb6a5b52c41d3df0f972db675022d152bd432fecbf1b5a68f66e3956cb5",
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "swiftsdl2",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/ctreffs/SwiftSDL2",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a",
|
|
||||||
"version" : "1.4.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 3
|
|
||||||
}
|
|
Binary file not shown.
@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Bucket
|
|
||||||
uuid = "64A8AF27-6696-4D7A-8C62-06216A95ECF0"
|
|
||||||
type = "1"
|
|
||||||
version = "2.0">
|
|
||||||
<Breakpoints>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "C2C839C6-26A1-468A-9479-A00FD57EA17C"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
nameForDebugger = "ignore-sigusr1"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "Yes"
|
|
||||||
filePath = "MeloNX/Core/Ryujinx.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "111"
|
|
||||||
endingLineNumber = "111"
|
|
||||||
landmarkName = "startWithRunLoop(config:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
<Actions>
|
|
||||||
<BreakpointActionProxy
|
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
|
||||||
<ActionContent
|
|
||||||
consoleCommand = "process handle SIGUSR1 -n true -p true -s false">
|
|
||||||
</ActionContent>
|
|
||||||
</BreakpointActionProxy>
|
|
||||||
</Actions>
|
|
||||||
<Locations>
|
|
||||||
<Location
|
|
||||||
uuid = "C2C839C6-26A1-468A-9479-A00FD57EA17C - e09e330dd17da5b1"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
symbolName = "MeloNX.RyujinxEmulator.startWithRunLoop(config: MeloNX.RyujinxEmulator.Configuration) throws -> ()"
|
|
||||||
moduleName = "MeloNX.debug.dylib"
|
|
||||||
usesParentBreakpointCondition = "Yes"
|
|
||||||
urlString = "file:///Users/stossy11/MeloNX/MeloNX-XC/MeloNX/Core/Ryujinx.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "111"
|
|
||||||
endingLineNumber = "111">
|
|
||||||
</Location>
|
|
||||||
<Location
|
|
||||||
uuid = "C2C839C6-26A1-468A-9479-A00FD57EA17C - a8ffb78cb80274ea"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
symbolName = "closure #2 @Sendable () -> () in MeloNX.RyujinxEmulator.startWithRunLoop(config: MeloNX.RyujinxEmulator.Configuration) throws -> ()"
|
|
||||||
moduleName = "MeloNX.debug.dylib"
|
|
||||||
usesParentBreakpointCondition = "Yes"
|
|
||||||
urlString = "file:///Users/stossy11/MeloNX/MeloNX-XC/MeloNX/Core/Ryujinx.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "112"
|
|
||||||
endingLineNumber = "112">
|
|
||||||
</Location>
|
|
||||||
</Locations>
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "5A4DEE32-2E63-4404-BFDE-AD7F1337DA66"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
nameForDebugger = "ignore-sigusr1"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "Yes"
|
|
||||||
filePath = "MeloNX/Core/Ryujinx.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "120"
|
|
||||||
endingLineNumber = "120"
|
|
||||||
landmarkName = "startWithRunLoop(config:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
<Actions>
|
|
||||||
<BreakpointActionProxy
|
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
|
||||||
<ActionContent
|
|
||||||
consoleCommand = "process handle SIGUSR1 -n true -p true -s false">
|
|
||||||
</ActionContent>
|
|
||||||
</BreakpointActionProxy>
|
|
||||||
</Actions>
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
</Breakpoints>
|
|
||||||
</Bucket>
|
|
@ -1,168 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 27/10/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import SDL2
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
var theWindow: UIWindow? = nil
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
@State var device: MTLDevice? = MTLCreateSystemDefaultDevice()
|
|
||||||
@State var gameUrl: URL?
|
|
||||||
@State var showFileImporter: Bool = false
|
|
||||||
@State var emulationStarted: Bool = false
|
|
||||||
@State var mainThread: Bool = true
|
|
||||||
|
|
||||||
@State var debugmode: Int = 0
|
|
||||||
|
|
||||||
init() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
SDL_SetMainReady()
|
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
if let gameUrl, emulationStarted {
|
|
||||||
VulkanSDLViewRepresentable { // displayid in
|
|
||||||
let config = RyujinxEmulator.Configuration(
|
|
||||||
inputPath: gameUrl.path,
|
|
||||||
mainThread: mainThread,
|
|
||||||
graphicsBackend: "Vulkan",
|
|
||||||
additionalArgs: [
|
|
||||||
//"--display-id", String(displayid),
|
|
||||||
// "--fullscreen", "true"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
showVirtualController(url: gameUrl, ryuconfig: config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack {
|
|
||||||
Text("NX iOS")
|
|
||||||
.font(.largeTitle)
|
|
||||||
.onTapGesture {
|
|
||||||
debugmode += 1
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
if debugmode > 9 {
|
|
||||||
Text("Debug Mode:")
|
|
||||||
.font(.title)
|
|
||||||
Text("Is on Main Thread?: \(mainThread)")
|
|
||||||
.font(.title2)
|
|
||||||
Toggle(isOn: $mainThread) {
|
|
||||||
Text("Use Main Thread")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
showFileImporter.toggle()
|
|
||||||
} label: {
|
|
||||||
Text("Select Game")
|
|
||||||
}
|
|
||||||
if let gameUrl {
|
|
||||||
Button {
|
|
||||||
emulationStarted = true
|
|
||||||
} label: {
|
|
||||||
Text("Go!")
|
|
||||||
}
|
|
||||||
.padding(8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.fileImporter(isPresented: $showFileImporter, allowedContentTypes: [.item]) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let url):
|
|
||||||
gameUrl = url
|
|
||||||
case .failure(let err):
|
|
||||||
print(err.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startEmulation(game: URL, config: RyujinxEmulator.Configuration) {
|
|
||||||
setenv("DOTNET_EnableDiagnostics", "0", 1)
|
|
||||||
setenv("HOME", String(validatingUTF8: getenv("HOME"))! + "/Documents", 1)
|
|
||||||
setenv("MVK_CONFIG_LOG_LEVEL", "4", 1)
|
|
||||||
|
|
||||||
let config = config
|
|
||||||
|
|
||||||
patchMakeKeyAndVisible()
|
|
||||||
// SDL_Init(SDL_INIT_VIDEO)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let emulator = RyujinxEmulator()
|
|
||||||
do {
|
|
||||||
try emulator.startWithRunLoop(config: config)
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func patchMakeKeyAndVisible() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let uiwindowClass = UIWindow.self
|
|
||||||
let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible))!
|
|
||||||
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible))!
|
|
||||||
method_exchangeImplementations(m1, m2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UIWindow {
|
|
||||||
@objc func wdb_makeKeyAndVisible() {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
|
|
||||||
print("Making window key and visible...")
|
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
|
||||||
}
|
|
||||||
self.wdb_makeKeyAndVisible()
|
|
||||||
theWindow = self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 15.0, *)
|
|
||||||
var g_gcVirtualController: GCVirtualController!
|
|
||||||
@available(iOS 15.0, *)
|
|
||||||
func showVirtualController(url: URL, ryuconfig: RyujinxEmulator.Configuration) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
|
|
||||||
print("Showing virtual controller...")
|
|
||||||
let config = GCVirtualController.Configuration()
|
|
||||||
config.elements = [
|
|
||||||
GCInputDirectionalDpad, GCInputButtonA, GCInputButtonB, GCInputButtonX, GCInputButtonY,
|
|
||||||
]
|
|
||||||
g_gcVirtualController = GCVirtualController(configuration: config)
|
|
||||||
g_gcVirtualController.connect { err in
|
|
||||||
print("Controller connect: \(String(describing: err))")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
startEmulation(game: url, config: ryuconfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 15.0, *)
|
|
||||||
func reconnectVirtualController() {
|
|
||||||
print("Reconnecting virtual controller...")
|
|
||||||
g_gcVirtualController.disconnect()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
g_gcVirtualController.connect { err in
|
|
||||||
print("Reconnected: err \(String(describing: err))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
//
|
|
||||||
// Ryujinx.h
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 1/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef RyujinxSDL_h
|
|
||||||
#define RyujinxSDL_h
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Declare the main_ryujinx_sdl function, matching the signature
|
|
||||||
int main_ryujinx_sdl(int argc, char **argv);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* RyujinxSDL_h */
|
|
@ -1,368 +0,0 @@
|
|||||||
//
|
|
||||||
// Ryujinx.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 27/10/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
enum RyujinxError: Error {
|
|
||||||
case libraryLoadError
|
|
||||||
case executionError(code: Int32)
|
|
||||||
case alreadyRunning
|
|
||||||
case notRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
class RyujinxEmulator {
|
|
||||||
private var isRunning = false
|
|
||||||
private var emulationThread: Thread?
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct Configuration {
|
|
||||||
let inputPath: String
|
|
||||||
let mainThread: Bool // i don't know why i added this
|
|
||||||
let graphicsBackend: String
|
|
||||||
var additionalArgs: [String]
|
|
||||||
|
|
||||||
init(
|
|
||||||
inputPath: String,
|
|
||||||
mainThread: Bool = true,
|
|
||||||
graphicsBackend: String = "Vulkan",
|
|
||||||
additionalArgs: [String] = []
|
|
||||||
) {
|
|
||||||
self.inputPath = inputPath
|
|
||||||
self.mainThread = mainThread
|
|
||||||
self.graphicsBackend = graphicsBackend
|
|
||||||
self.additionalArgs = additionalArgs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func start(with config: Configuration) throws {
|
|
||||||
|
|
||||||
var args: [String] = []
|
|
||||||
// Taken from the POC
|
|
||||||
/*
|
|
||||||
var args: [String] = [
|
|
||||||
"--enable-debug-logs", "false", "--enable-trace-logs", "false", "--memory-manager-mode",
|
|
||||||
"SoftwarePageTable",
|
|
||||||
"--graphics-backend",
|
|
||||||
"Vulkan",
|
|
||||||
//"--enable-fs-integrity-checks", "false",
|
|
||||||
"--input-id-1", "0",
|
|
||||||
// "--list-inputs-ids", "true",
|
|
||||||
config.inputPath,
|
|
||||||
]
|
|
||||||
*/
|
|
||||||
|
|
||||||
args.append(config.inputPath)
|
|
||||||
args.append("--graphics-backend")
|
|
||||||
args.append(config.graphicsBackend)
|
|
||||||
// args.append(contentsOf: ["--memory-manager-mode", "SoftwarePageTable"])
|
|
||||||
// args.append(contentsOf: ["--fullscreen", "true"])
|
|
||||||
args.append(contentsOf: ["--enable-debug-logs", "true"])
|
|
||||||
args.append(contentsOf: ["--enable-trace-logs", "true"])
|
|
||||||
// args.append(contentsOf: ["--list-inputs-ids", "true"])
|
|
||||||
args.append(contentsOf: ["--input-id-1", "1-47150005-05ac-0000-0100-00004f066d01"])
|
|
||||||
// args.append("--input-path")
|
|
||||||
|
|
||||||
args.append(contentsOf: config.additionalArgs)
|
|
||||||
|
|
||||||
let cArgs = args.map { strdup($0) }
|
|
||||||
defer {
|
|
||||||
cArgs.forEach { ptr in
|
|
||||||
if let ptr = ptr {
|
|
||||||
free(ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var argvPtrs = cArgs
|
|
||||||
|
|
||||||
|
|
||||||
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
|
||||||
|
|
||||||
if result != 0 {
|
|
||||||
throw RyujinxError.executionError(code: result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cray z
|
|
||||||
func startWithRunLoop(config: Configuration) throws {
|
|
||||||
guard !isRunning else {
|
|
||||||
throw RyujinxError.alreadyRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning = true
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
do {
|
|
||||||
try Self.start(with: config)
|
|
||||||
} catch {
|
|
||||||
Self.log("Emulation failed to start: \(error)")
|
|
||||||
self.isRunning = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emulationThread = Thread {
|
|
||||||
let runLoop = RunLoop.current
|
|
||||||
|
|
||||||
let port = Port()
|
|
||||||
runLoop.add(port, forMode: .default)
|
|
||||||
|
|
||||||
print(config.mainThread ? "Running on the main thread" : "Running on the background thread")
|
|
||||||
/*
|
|
||||||
if config.mainThread {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
do {
|
|
||||||
try Self.start(with: config)
|
|
||||||
} catch {
|
|
||||||
Self.log("Emulation failed to start: \(error)")
|
|
||||||
self.isRunning = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
do {
|
|
||||||
try Self.start(with: config)
|
|
||||||
} catch {
|
|
||||||
Self.log("Emulation failed to start: \(error)")
|
|
||||||
self.isRunning = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
while self.isRunning && runLoop.run(mode: .default, before: .distantFuture) {
|
|
||||||
autoreleasepool { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Self.log("Emulation loop ended")
|
|
||||||
}
|
|
||||||
|
|
||||||
emulationThread?.name = "RyujinxEmulationThread"
|
|
||||||
emulationThread?.qualityOfService = .userInteractive
|
|
||||||
emulationThread?.threadPriority = 0.9
|
|
||||||
// emulationThread?.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func quickStart(romPath: String) throws {
|
|
||||||
let config = Configuration(inputPath: romPath)
|
|
||||||
try startWithRunLoop(config: config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stops the emulator
|
|
||||||
func stop() throws {
|
|
||||||
guard isRunning else {
|
|
||||||
throw RyujinxError.notRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning = false
|
|
||||||
emulationThread?.cancel()
|
|
||||||
emulationThread = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var running: Bool {
|
|
||||||
return isRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
static func log(_ message: String) {
|
|
||||||
print("[Ryujinx] \(message)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RyujinxEmulator.Configuration {
|
|
||||||
var toCommandLineArgs: [String] {
|
|
||||||
var args: [String] = []
|
|
||||||
|
|
||||||
args.append(inputPath)
|
|
||||||
|
|
||||||
// if enableKeyboard {
|
|
||||||
// args.append("--enable-keyboard")
|
|
||||||
// }
|
|
||||||
|
|
||||||
args.append("--graphics-backend")
|
|
||||||
args.append(graphicsBackend)
|
|
||||||
|
|
||||||
args.append(contentsOf: additionalArgs)
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create configuration from command line arguments
|
|
||||||
static func fromCommandLineArgs(_ args: [String]) -> RyujinxEmulator.Configuration? {
|
|
||||||
var inputPath: String?
|
|
||||||
var enableKeyboard = false
|
|
||||||
var graphicsBackend = "Vulkan"
|
|
||||||
var additionalArgs: [String] = []
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while i < args.count {
|
|
||||||
switch args[i] {
|
|
||||||
case "--enable-keyboard":
|
|
||||||
enableKeyboard = true
|
|
||||||
case "--graphics-backend":
|
|
||||||
i += 1
|
|
||||||
if i < args.count {
|
|
||||||
graphicsBackend = args[i]
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
additionalArgs.append(args[i])
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let inputPath = inputPath else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return RyujinxEmulator.Configuration(
|
|
||||||
inputPath: inputPath,
|
|
||||||
mainThread: enableKeyboard,
|
|
||||||
graphicsBackend: graphicsBackend,
|
|
||||||
additionalArgs: additionalArgs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Code Taken from POC
|
|
||||||
var g_HookMmapReserved4GB: UnsafeMutableRawPointer! = nil
|
|
||||||
var g_HookMmapReservedJitCache: UnsafeMutableRawPointer! = nil
|
|
||||||
|
|
||||||
func initHookMmap() -> Bool {
|
|
||||||
// Hack: if out of memory, you can reserve less (e.g. around 0xc000_0000 or even 0x8000_0000) but it'll crash later
|
|
||||||
let reserve4GBSize = 0x1_0000_0000
|
|
||||||
g_HookMmapReserved4GB = mmap(
|
|
||||||
nil, reserve4GBSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
|
|
||||||
if g_HookMmapReserved4GB == MAP_FAILED {
|
|
||||||
print("can't allocate 4gb")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let reserveJitCacheSize = 0x8000_0000
|
|
||||||
g_HookMmapReservedJitCache = mmap(
|
|
||||||
nil, reserveJitCacheSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
|
|
||||||
if g_HookMmapReservedJitCache == MAP_FAILED {
|
|
||||||
print("can't allocate jit cache")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !reallocateAreaWithOwnership(address: g_HookMmapReserved4GB, size: reserve4GBSize) {
|
|
||||||
print("can't reallocate area with ownership for 4gb")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !reallocateAreaWithOwnership(address: g_HookMmapReservedJitCache, size: reserveJitCacheSize) {
|
|
||||||
print("can't reallocate area with ownership for jitcache")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Allocated Needed Ram")
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func hookMmap(
|
|
||||||
addr: UnsafeMutableRawPointer?, len: Int, prot: Int32, flags: Int32, fd: Int32, offset: off_t
|
|
||||||
) -> UnsafeMutableRawPointer! {
|
|
||||||
print("mmap hook! \(String(describing: addr)) \(len) \(prot) \(flags)")
|
|
||||||
// TODO(zhuowei): threads?
|
|
||||||
if g_HookMmapReserved4GB != nil && len == 0x1_0000_0000 {
|
|
||||||
let ret = g_HookMmapReserved4GB
|
|
||||||
g_HookMmapReserved4GB = nil
|
|
||||||
print("returning 4gb: \(ret!)")
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
if g_HookMmapReservedJitCache != nil && len == 0x7ff0_0000 {
|
|
||||||
// Hack: it wants 2GB; give it smaller
|
|
||||||
let ret = g_HookMmapReservedJitCache
|
|
||||||
g_HookMmapReservedJitCache = nil
|
|
||||||
print("returning jitcache: \(ret!)")
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
return mmap(addr, len, prot, flags, fd, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reallocateAreaWithOwnership(address: UnsafeMutableRawPointer, size: Int) -> Bool {
|
|
||||||
let addressBase: mach_vm_address_t = mach_vm_address_t(UInt(bitPattern: address))
|
|
||||||
let mapChunkSize = 128 * 1024 * 1024
|
|
||||||
for off in stride(from: 0, to: size, by: mapChunkSize) {
|
|
||||||
let targetSize = memory_object_size_t(min(mapChunkSize, size - off))
|
|
||||||
var memoryObjectSize = targetSize
|
|
||||||
var memoryObjectPort: mach_port_t = 0
|
|
||||||
let err = mach_make_memory_entry_64(
|
|
||||||
mach_task_self_, &memoryObjectSize, 0,
|
|
||||||
MAP_MEM_NAMED_CREATE | MAP_MEM_LEDGER_TAGGED | VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
|
|
||||||
&memoryObjectPort, /*parent_entry=*/ 0)
|
|
||||||
if err != 0 {
|
|
||||||
print("mach_make_memory_entry_64 returned error: \(String(cString: mach_error_string(err)!))")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer { mach_port_deallocate(mach_task_self_, memoryObjectPort) }
|
|
||||||
if memoryObjectSize != targetSize {
|
|
||||||
print("size is wrong?! \(memoryObjectSize) \(targetSize)")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let err2 = mach_memory_entry_ownership(
|
|
||||||
memoryObjectPort, TASK_NULL, VM_LEDGER_TAG_DEFAULT, VM_LEDGER_FLAG_NO_FOOTPRINT)
|
|
||||||
if err2 != 0 {
|
|
||||||
print(
|
|
||||||
"mach_memory_entry_ownership returned error: \(String(cString: mach_error_string(err2)!))")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let targetMapAddress: vm_address_t = vm_address_t(addressBase) + vm_address_t(off)
|
|
||||||
var mapAddress = targetMapAddress
|
|
||||||
let err3 = vm_map(
|
|
||||||
mach_task_self_, &mapAddress, vm_size_t(memoryObjectSize), /*mask=*/ 0, /*flags=*/
|
|
||||||
VM_FLAGS_OVERWRITE,
|
|
||||||
memoryObjectPort, /*offset=*/ 0, /*copy=*/ 0, VM_PROT_READ | VM_PROT_WRITE,
|
|
||||||
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE, VM_INHERIT_COPY)
|
|
||||||
if err3 != 0 {
|
|
||||||
print("vm_map returned error: \(String(cString: mach_error_string(err3)!))")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if mapAddress != targetMapAddress {
|
|
||||||
print("map address wrong")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias SystemNative_Open_Type = @convention(c) (
|
|
||||||
_ path: UnsafePointer<CChar>, _ flags: Int32, _ mode: Int32
|
|
||||||
) -> Int
|
|
||||||
|
|
||||||
var real_SystemNative_Open: SystemNative_Open_Type!
|
|
||||||
func hook_SystemNative_Open(path: UnsafePointer<CChar>, flags: Int32, mode: Int32) -> Int {
|
|
||||||
let fileName = String(cString: path)
|
|
||||||
print("opening \(fileName)")
|
|
||||||
return real_SystemNative_Open(path, flags, mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func pInvokeOverride(libraryName: UnsafePointer<CChar>!, entrypointName: UnsafePointer<CChar>!)
|
|
||||||
-> UnsafeRawPointer?
|
|
||||||
{
|
|
||||||
let libraryName = String(cString: libraryName)
|
|
||||||
let entrypointName = String(cString: entrypointName)
|
|
||||||
// print(libraryName, entrypointName)
|
|
||||||
if entrypointName == "mmap" {
|
|
||||||
typealias MmapType = @convention(c) (
|
|
||||||
_: UnsafeMutableRawPointer?, _: Int, _: Int32, _: Int32, _: Int32, _: off_t
|
|
||||||
) -> UnsafeMutableRawPointer?
|
|
||||||
return unsafeBitCast(hookMmap as MmapType, to: UnsafeRawPointer.self)
|
|
||||||
} else if entrypointName == "SystemNative_Open" {
|
|
||||||
let handle = dlopen("libSystem.Native.dylib", RTLD_LOCAL | RTLD_LAZY)
|
|
||||||
real_SystemNative_Open = unsafeBitCast(
|
|
||||||
dlsym(handle, "SystemNative_Open"), to: SystemNative_Open_Type.self)
|
|
||||||
return unsafeBitCast(
|
|
||||||
hook_SystemNative_Open as SystemNative_Open_Type, to: UnsafeRawPointer.self)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libSystem.Globalization.Native.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libSystem.IO.Compression.Native.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libSystem.IO.Ports.Native.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libSystem.Native.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libSystem.Net.Security.Native.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libSystem.Security.Cryptography.Native.Apple.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libclrjit.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>AvailableLibraries</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>LibraryIdentifier</key>
|
|
||||||
<string>ios-arm64</string>
|
|
||||||
<key>LibraryPath</key>
|
|
||||||
<string>libcoreclr.dylib</string>
|
|
||||||
<key>SupportedArchitectures</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>SupportedPlatform</key>
|
|
||||||
<string>ios</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>XFWK</string>
|
|
||||||
<key>XCFrameworkFormatVersion</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
@ -1,8 +0,0 @@
|
|||||||
//
|
|
||||||
// MeloNX-Bridging-Header.h
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 1/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "../Core/Ryujinx.h"
|
|
@ -1,17 +0,0 @@
|
|||||||
//
|
|
||||||
// MeloNXApp.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 27/10/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
@main
|
|
||||||
struct MeloNXApp: App {
|
|
||||||
var body: some Scene {
|
|
||||||
WindowGroup {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,297 +0,0 @@
|
|||||||
//
|
|
||||||
// MetalVIew.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 27/10/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import Metal
|
|
||||||
import MetalKit
|
|
||||||
import UIKit
|
|
||||||
import SDL2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct VulkanSDLViewRepresentable: UIViewRepresentable {
|
|
||||||
|
|
||||||
let configure: () -> Void
|
|
||||||
func makeUIView(context: Context) -> VulkanSDLView {
|
|
||||||
|
|
||||||
configure()
|
|
||||||
|
|
||||||
let view = VulkanSDLView(frame: .zero)
|
|
||||||
return view
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: VulkanSDLView, context: Context) {
|
|
||||||
// Handle any updates if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VulkanSDLView: UIView {
|
|
||||||
var sdlWindow: OpaquePointer?
|
|
||||||
var metalView: UnsafeMutableRawPointer?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
initializeSDL()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
initializeSDL()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func initializeSDL() {
|
|
||||||
// Initialize SDL with video support
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create an SDL window with Metal support
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
sdlWindow = SDL_GetWindowFromID(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
guard sdlWindow != nil else {
|
|
||||||
print("Error creating SDL window: \(String(cString: SDL_GetError()))")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create SDL Metal view and attach to this UIView
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
metalView = SDL_Metal_CreateView(sdlWindow)
|
|
||||||
if metalView == nil {
|
|
||||||
print("Failed to create SDL Metal view.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
if let metalLayerPointer = SDL_Metal_GetLayer(metalView) {
|
|
||||||
let metalLayer = Unmanaged<CAMetalLayer>.fromOpaque(metalLayerPointer).takeUnretainedValue()
|
|
||||||
metalLayer.device = MTLCreateSystemDefaultDevice()
|
|
||||||
// metalLayer.pixelFormat = .bgra8Unorm
|
|
||||||
layer.addSublayer(metalLayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
if let metalView = metalView {
|
|
||||||
SDL_Metal_DestroyView(metalView)
|
|
||||||
}
|
|
||||||
if let sdlWindow = sdlWindow {
|
|
||||||
SDL_DestroyWindow(sdlWindow)
|
|
||||||
}
|
|
||||||
SDL_Quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MetalView: UIViewRepresentable {
|
|
||||||
let device: MTLDevice?
|
|
||||||
let configure: (UIView) -> Void
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> SudachiScreenView {
|
|
||||||
let view = SudachiScreenView()
|
|
||||||
configure(view.primaryScreen)
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: SudachiScreenView, context: Context) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class SudachiScreenView: UIView {
|
|
||||||
var primaryScreen: UIView!
|
|
||||||
var portraitconstraints = [NSLayoutConstraint]()
|
|
||||||
var landscapeconstraints = [NSLayoutConstraint]()
|
|
||||||
var fullscreenconstraints = [NSLayoutConstraint]()
|
|
||||||
let userDefaults = UserDefaults.standard
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
if userDefaults.bool(forKey: "isfullscreen") {
|
|
||||||
// setupSudachiScreenforcools()
|
|
||||||
setupSudachiScreen2()
|
|
||||||
} else if userDefaults.bool(forKey: "isairplay") {
|
|
||||||
setupSudachiScreen2()
|
|
||||||
} else if userDefaults.bool(forKey: "169fullscreen") { // this is for the 16/9 aspect ratio full screen
|
|
||||||
setupSudachiScreenforcools()
|
|
||||||
} else if UIDevice.current.userInterfaceIdiom == .pad {
|
|
||||||
setupSudachiScreenforiPad()
|
|
||||||
} else {
|
|
||||||
setupSudachiScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
if userDefaults.bool(forKey: "isfullscreen") {
|
|
||||||
setupSudachiScreen2()
|
|
||||||
} else if userDefaults.bool(forKey: "isairplay") {
|
|
||||||
setupSudachiScreen2()
|
|
||||||
} else if UIDevice.current.userInterfaceIdiom == .pad {
|
|
||||||
setupSudachiScreenforiPad()
|
|
||||||
} else {
|
|
||||||
setupSudachiScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func setupSudachiScreen2() {
|
|
||||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
|
||||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
primaryScreen.clipsToBounds = true
|
|
||||||
addSubview(primaryScreen)
|
|
||||||
|
|
||||||
fullscreenconstraints = [
|
|
||||||
primaryScreen.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
primaryScreen.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
primaryScreen.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
primaryScreen.bottomAnchor.constraint(equalTo: bottomAnchor)
|
|
||||||
]
|
|
||||||
|
|
||||||
addConstraints(fullscreenconstraints)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupSudachiScreenforcools() { // oh god this took a long time, im going insane
|
|
||||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
|
||||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
primaryScreen.clipsToBounds = true
|
|
||||||
|
|
||||||
addSubview(primaryScreen)
|
|
||||||
|
|
||||||
primaryScreen.layer.cornerRadius = 5
|
|
||||||
primaryScreen.layer.masksToBounds = true
|
|
||||||
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
primaryScreen.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
||||||
primaryScreen.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
||||||
primaryScreen.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
|
|
||||||
primaryScreen.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
|
|
||||||
])
|
|
||||||
|
|
||||||
let aspectRatio: CGFloat = 16.0/9.0
|
|
||||||
let aspectRatioConstraint = NSLayoutConstraint(
|
|
||||||
item: primaryScreen ?? UIView(),
|
|
||||||
attribute: .width,
|
|
||||||
relatedBy: .equal,
|
|
||||||
toItem: primaryScreen,
|
|
||||||
attribute: .height,
|
|
||||||
multiplier: aspectRatio,
|
|
||||||
constant: 0
|
|
||||||
)
|
|
||||||
aspectRatioConstraint.priority = .required - 1
|
|
||||||
primaryScreen.addConstraint(aspectRatioConstraint)
|
|
||||||
|
|
||||||
let heightConstraint = primaryScreen.heightAnchor.constraint(equalTo: heightAnchor)
|
|
||||||
heightConstraint.priority = .defaultHigh
|
|
||||||
let widthConstraint = primaryScreen.widthAnchor.constraint(equalTo: widthAnchor)
|
|
||||||
widthConstraint.priority = .defaultHigh
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([heightConstraint, widthConstraint])
|
|
||||||
|
|
||||||
// Make primaryScreen fill container
|
|
||||||
fullscreenconstraints = [
|
|
||||||
primaryScreen.topAnchor.constraint(equalTo: primaryScreen.topAnchor),
|
|
||||||
primaryScreen.bottomAnchor.constraint(equalTo: primaryScreen.bottomAnchor),
|
|
||||||
primaryScreen.leadingAnchor.constraint(equalTo: primaryScreen.leadingAnchor),
|
|
||||||
primaryScreen.trailingAnchor.constraint(equalTo: primaryScreen.trailingAnchor)
|
|
||||||
]
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate(fullscreenconstraints)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupSudachiScreenforiPad() {
|
|
||||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
|
||||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
primaryScreen.clipsToBounds = true
|
|
||||||
primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor
|
|
||||||
primaryScreen.layer.borderWidth = 3
|
|
||||||
primaryScreen.layer.cornerCurve = .continuous
|
|
||||||
primaryScreen.layer.cornerRadius = 10
|
|
||||||
addSubview(primaryScreen)
|
|
||||||
|
|
||||||
|
|
||||||
portraitconstraints = [
|
|
||||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
|
|
||||||
primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10),
|
|
||||||
primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10),
|
|
||||||
primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16),
|
|
||||||
]
|
|
||||||
|
|
||||||
landscapeconstraints = [
|
|
||||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 50),
|
|
||||||
primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -100),
|
|
||||||
primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9),
|
|
||||||
primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
updateConstraintsForOrientation()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func setupSudachiScreen() {
|
|
||||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
|
||||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
primaryScreen.clipsToBounds = true
|
|
||||||
primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor
|
|
||||||
primaryScreen.layer.borderWidth = 3
|
|
||||||
primaryScreen.layer.cornerCurve = .continuous
|
|
||||||
primaryScreen.layer.cornerRadius = 10
|
|
||||||
addSubview(primaryScreen)
|
|
||||||
|
|
||||||
|
|
||||||
portraitconstraints = [
|
|
||||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
|
|
||||||
primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10),
|
|
||||||
primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10),
|
|
||||||
primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16),
|
|
||||||
]
|
|
||||||
|
|
||||||
landscapeconstraints = [
|
|
||||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
|
|
||||||
primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10),
|
|
||||||
primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9),
|
|
||||||
primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
|
|
||||||
]
|
|
||||||
|
|
||||||
updateConstraintsForOrientation()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
updateConstraintsForOrientation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateConstraintsForOrientation() {
|
|
||||||
|
|
||||||
if userDefaults.bool(forKey: "isfullscreen") {
|
|
||||||
removeConstraints(portraitconstraints)
|
|
||||||
removeConstraints(landscapeconstraints)
|
|
||||||
removeConstraints(fullscreenconstraints)
|
|
||||||
addConstraints(fullscreenconstraints)
|
|
||||||
} else {
|
|
||||||
removeConstraints(portraitconstraints)
|
|
||||||
removeConstraints(landscapeconstraints)
|
|
||||||
|
|
||||||
let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
|
|
||||||
addConstraints(isPortrait ? portraitconstraints : landscapeconstraints)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,13 +10,13 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices.
|
MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices.
|
||||||
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">MIT license</a>. <br />
|
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">MIT license</a>. <br
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
As of October 2024, MeloNX can only play the audio of games.
|
MeloNX works on iPhone X and later and iPad 7th Gen and later. A lot of games work.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To run MeloNX on your iOS device, at least 8GB of RAM is recommended to ensure stability. For full instructions, refer to our [Setup Guide](https://github.com/MeloNX-Emu/MeloNX/wiki/Setup-Guide).
|
To run MeloNX on your iOS device, at least 4GB of RAM is recommended to ensure stability. For full instructions, refer to our [Setup Guide](https://github.com/MeloNX-Emu/MeloNX/wiki/Setup-Guide).
|
||||||
|
18
compile.sh
Executable file
18
compile.sh
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
# Define the destination directory (hardcoded)
|
||||||
|
DESTINATION_DIR="src/MeloNX/Dependencies/Dynamic\ Libraries/Ryujinx.Headless.SDL2.dylib"
|
||||||
|
|
||||||
|
# Restore the project
|
||||||
|
dotnet restore
|
||||||
|
|
||||||
|
# Build the project with the specified version
|
||||||
|
dotnet build -c Release
|
||||||
|
|
||||||
|
# Publish the project with the specified runtime and settings
|
||||||
|
dotnet publish -c Release -r ios-arm64 -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||||
|
|
||||||
|
# Move the published .dylib to the specified location
|
||||||
|
mv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\ Libraries/Ryujinx.Headless.SDL2.dylib
|
||||||
|
|
@ -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;
|
||||||
@ -189,7 +189,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
||||||
|
|
||||||
Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
|
//DEBUG: Show JIT Memory Allocation
|
||||||
|
|
||||||
|
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
|
||||||
|
|
||||||
if (allocOffset < 0)
|
if (allocOffset < 0)
|
||||||
{
|
{
|
||||||
|
1267
src/MeloNX/MeloNX.xcodeproj/project.pbxproj
Normal file
1267
src/MeloNX/MeloNX.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "1b46f7a56d6f994a826e31441c25b929398800cf38b3e9be23ae6e0ef8fc32c7",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "swiftsvg",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/mchoe/SwiftSVG",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftuijoystick",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
|
||||||
|
"version" : "1.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
@ -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.
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1600"
|
LastUpgradeVersion = "1610"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@ -15,7 +15,7 @@
|
|||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "4E87E28B2CCE2C1000F54325"
|
BlueprintIdentifier = "4E80A98C2CD6F54500029585"
|
||||||
BuildableName = "MeloNX.app"
|
BuildableName = "MeloNX.app"
|
||||||
BlueprintName = "MeloNX"
|
BlueprintName = "MeloNX"
|
||||||
ReferencedContainer = "container:MeloNX.xcodeproj">
|
ReferencedContainer = "container:MeloNX.xcodeproj">
|
||||||
@ -29,9 +29,33 @@
|
|||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
shouldAutocreateTestPlan = "YES">
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4E80A99C2CD6F54700029585"
|
||||||
|
BuildableName = "MeloNXTests.xctest"
|
||||||
|
BlueprintName = "MeloNXTests"
|
||||||
|
ReferencedContainer = "container:MeloNX.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4E80A9A62CD6F54700029585"
|
||||||
|
BuildableName = "MeloNXUITests.xctest"
|
||||||
|
BlueprintName = "MeloNXUITests"
|
||||||
|
ReferencedContainer = "container:MeloNX.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
@ -39,12 +63,14 @@
|
|||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
showGraphicsOverview = "Yes"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "4E87E28B2CCE2C1000F54325"
|
BlueprintIdentifier = "4E80A98C2CD6F54500029585"
|
||||||
BuildableName = "MeloNX.app"
|
BuildableName = "MeloNX.app"
|
||||||
BlueprintName = "MeloNX"
|
BlueprintName = "MeloNX"
|
||||||
ReferencedContainer = "container:MeloNX.xcodeproj">
|
ReferencedContainer = "container:MeloNX.xcodeproj">
|
||||||
@ -61,7 +87,7 @@
|
|||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "4E87E28B2CCE2C1000F54325"
|
BlueprintIdentifier = "4E80A98C2CD6F54500029585"
|
||||||
BuildableName = "MeloNX.app"
|
BuildableName = "MeloNX.app"
|
||||||
BlueprintName = "MeloNX"
|
BlueprintName = "MeloNX"
|
||||||
ReferencedContainer = "container:MeloNX.xcodeproj">
|
ReferencedContainer = "container:MeloNX.xcodeproj">
|
@ -9,13 +9,15 @@
|
|||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>3</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>4E87E28B2CCE2C1000F54325</key>
|
<key>orderHint</key>
|
||||||
<dict>
|
<integer>4</integer>
|
||||||
<key>primary</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Bucket
|
||||||
|
uuid = "271EB822-2830-4016-A3D7-CA2DEBEDCD27"
|
||||||
|
type = "1"
|
||||||
|
version = "2.0">
|
||||||
|
<Breakpoints>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "499F5405-B63B-4623-9332-1E44FC449FD0"
|
||||||
|
shouldBeEnabled = "No"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "MeloNX/Views/GamesList/GameListView.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "309"
|
||||||
|
endingLineNumber = "309"
|
||||||
|
landmarkName = "loadGames()"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "0BB7C122-8933-48E8-ABA3-1ABB39594258"
|
||||||
|
shouldBeEnabled = "No"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "MeloNX/Models/Game.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "37"
|
||||||
|
endingLineNumber = "37"
|
||||||
|
landmarkName = "createImage(from:)"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
</Breakpoints>
|
||||||
|
</Bucket>
|
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>4E80A98C2CD6F54500029585</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>4E80A99C2CD6F54700029585</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>4E80A9A62CD6F54700029585</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
60
src/MeloNX/MeloNX/App/Core/Ryujinx/MetalHUD/MTLHUD.swift
Normal file
60
src/MeloNX/MeloNX/App/Core/Ryujinx/MetalHUD/MTLHUD.swift
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// MTLHUD.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 26/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class MTLHud {
|
||||||
|
|
||||||
|
var canMetalHud: Bool {
|
||||||
|
return openMetalDylib()
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEnabled: Bool {
|
||||||
|
if let getenv = getenv("MTL_HUD_ENABLED") {
|
||||||
|
return String(cString: getenv).contains("1")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
static let shared = MTLHud()
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
openMetalDylib()
|
||||||
|
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||||
|
enable()
|
||||||
|
} else {
|
||||||
|
disable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openMetalDylib() -> Bool {
|
||||||
|
let path = "/usr/lib/libMTLHud.dylib"
|
||||||
|
|
||||||
|
// Load the dynamic library
|
||||||
|
if dlopen(path, RTLD_NOW) != nil {
|
||||||
|
// Library loaded successfully
|
||||||
|
print("Library loaded from \(path)")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// Handle error
|
||||||
|
if let error = String(validatingUTF8: dlerror()) {
|
||||||
|
print("Error loading library: \(error)")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func enable() {
|
||||||
|
setenv("MTL_HUD_ENABLED", "1", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func disable() {
|
||||||
|
setenv("MTL_HUD_ENABLED", "0", 1)
|
||||||
|
}
|
||||||
|
}
|
316
src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift
Normal file
316
src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
//
|
||||||
|
// Ryujinx.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
import GameController
|
||||||
|
|
||||||
|
struct Controller: Identifiable, Hashable {
|
||||||
|
var id: String
|
||||||
|
var name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct iOSNav<Content: View>: View {
|
||||||
|
@ViewBuilder var content: () -> Content
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if #available(iOS 16, *) {
|
||||||
|
NavigationStack(root: content)
|
||||||
|
} else {
|
||||||
|
NavigationView(content: content)
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
.navigationViewStyle(.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ryujinx {
|
||||||
|
private var isRunning = false
|
||||||
|
|
||||||
|
let virtualController = VirtualController()
|
||||||
|
|
||||||
|
@Published var controllerMap: [Controller] = []
|
||||||
|
@State var firmwareversion = "0"
|
||||||
|
|
||||||
|
static let shared = Ryujinx()
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
public struct Configuration : Codable, Equatable {
|
||||||
|
var gamepath: String
|
||||||
|
var inputids: [String]
|
||||||
|
var resscale: Float
|
||||||
|
var debuglogs: Bool
|
||||||
|
var tracelogs: Bool
|
||||||
|
var nintendoinput: Bool
|
||||||
|
var enableInternet: Bool
|
||||||
|
var listinputids: Bool
|
||||||
|
var fullscreen: Bool
|
||||||
|
var memoryManagerMode: String
|
||||||
|
var disableShaderCache: Bool
|
||||||
|
var disableDockedMode: Bool
|
||||||
|
var enableTextureRecompression: Bool
|
||||||
|
var additionalArgs: [String]
|
||||||
|
|
||||||
|
|
||||||
|
init(gamepath: String,
|
||||||
|
inputids: [String] = [],
|
||||||
|
debuglogs: Bool = false,
|
||||||
|
tracelogs: Bool = false,
|
||||||
|
listinputids: Bool = false,
|
||||||
|
fullscreen: Bool = true,
|
||||||
|
memoryManagerMode: String = "HostMapped",
|
||||||
|
disableShaderCache: Bool = false,
|
||||||
|
disableDockedMode: Bool = false,
|
||||||
|
nintendoinput: Bool = true,
|
||||||
|
enableInternet: Bool = false,
|
||||||
|
enableTextureRecompression: Bool = true,
|
||||||
|
additionalArgs: [String] = [],
|
||||||
|
resscale: Float = 1.00
|
||||||
|
) {
|
||||||
|
self.gamepath = gamepath
|
||||||
|
self.inputids = inputids
|
||||||
|
self.debuglogs = debuglogs
|
||||||
|
self.tracelogs = tracelogs
|
||||||
|
self.listinputids = listinputids
|
||||||
|
self.fullscreen = fullscreen
|
||||||
|
self.disableShaderCache = disableShaderCache
|
||||||
|
self.disableDockedMode = disableDockedMode
|
||||||
|
self.enableTextureRecompression = enableTextureRecompression
|
||||||
|
self.additionalArgs = additionalArgs
|
||||||
|
self.memoryManagerMode = memoryManagerMode
|
||||||
|
self.resscale = resscale
|
||||||
|
self.nintendoinput = nintendoinput
|
||||||
|
self.enableInternet = enableInternet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func start(with config: Configuration) throws {
|
||||||
|
guard !isRunning else {
|
||||||
|
throw RyujinxError.alreadyRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning = true
|
||||||
|
|
||||||
|
RunLoop.current.perform {
|
||||||
|
let url = URL(string: config.gamepath)!
|
||||||
|
|
||||||
|
do {
|
||||||
|
let args = self.buildCommandLineArgs(from: config)
|
||||||
|
let accessing = url.startAccessingSecurityScopedResource()
|
||||||
|
|
||||||
|
// Convert Arguments to ones that Ryujinx can Read
|
||||||
|
let cArgs = args.map { strdup($0) }
|
||||||
|
defer { cArgs.forEach { free($0) } }
|
||||||
|
var argvPtrs = cArgs
|
||||||
|
|
||||||
|
// Start the emulation
|
||||||
|
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
||||||
|
|
||||||
|
if result != 0 {
|
||||||
|
self.isRunning = false
|
||||||
|
if accessing {
|
||||||
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw RyujinxError.executionError(code: result)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.isRunning = false
|
||||||
|
Self.log("Emulation failed to start: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func stop() throws {
|
||||||
|
guard isRunning else {
|
||||||
|
throw RyujinxError.notRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var running: Bool {
|
||||||
|
return isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
private func buildCommandLineArgs(from config: Configuration) -> [String] {
|
||||||
|
var args: [String] = []
|
||||||
|
|
||||||
|
// Add the game path
|
||||||
|
args.append(config.gamepath)
|
||||||
|
|
||||||
|
// Starts with vulkan
|
||||||
|
args.append("--graphics-backend")
|
||||||
|
args.append("Vulkan")
|
||||||
|
|
||||||
|
args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode])
|
||||||
|
|
||||||
|
args.append(contentsOf: ["--exclusive-fullscreen", String(true)])
|
||||||
|
args.append(contentsOf: ["--exclusive-fullscreen-width", "\(Int(UIScreen.main.bounds.width))"])
|
||||||
|
args.append(contentsOf: ["--exclusive-fullscreen-height", "\(Int(UIScreen.main.bounds.height))"])
|
||||||
|
// We don't need this. Ryujinx should handle it fine :3
|
||||||
|
|
||||||
|
if config.fullscreen {
|
||||||
|
args.append(contentsOf: ["--aspect-ratio", "Stretched"])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if config.nintendoinput {
|
||||||
|
args.append("--correct-controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
args.append("--disable-vsync")
|
||||||
|
|
||||||
|
|
||||||
|
if config.resscale != 1.0 {
|
||||||
|
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.disableShaderCache { // same with disableShaderCache
|
||||||
|
args.append("--disable-shader-cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.disableDockedMode { // disableDockedMode is actually enableDockedMode, i just have flipped it around in the settings page to make it easier to understand :3
|
||||||
|
args.append("--disable-docked-mode")
|
||||||
|
}
|
||||||
|
if config.enableTextureRecompression {
|
||||||
|
args.append("--enable-texture-recompression")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.debuglogs {
|
||||||
|
args.append(contentsOf: ["--enable-debug-logs"])
|
||||||
|
}
|
||||||
|
if config.tracelogs {
|
||||||
|
args.append(contentsOf: ["--enable-trace-logs"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// List the input ids
|
||||||
|
if config.listinputids {
|
||||||
|
args.append(contentsOf: ["--list-inputs-ids"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the input ids (limit to 4 just in case)
|
||||||
|
if !config.inputids.isEmpty {
|
||||||
|
config.inputids.prefix(4).enumerated().forEach { index, inputId in
|
||||||
|
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apped any additional arguments
|
||||||
|
args.append(contentsOf: config.additionalArgs)
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchFirmwareVersion() -> String {
|
||||||
|
do {
|
||||||
|
let firmwareVersionPointer = installed_firmware_version()
|
||||||
|
if let pointer = firmwareVersionPointer {
|
||||||
|
let firmwareVersion = String(cString: pointer)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.firmwareversion = firmwareVersion
|
||||||
|
}
|
||||||
|
return firmwareVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func installFirmware(firmwarePath: String) {
|
||||||
|
guard let cString = firmwarePath.cString(using: .utf8) else {
|
||||||
|
print("Invalid firmware path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
install_firmware(cString)
|
||||||
|
|
||||||
|
let version = fetchFirmwareVersion()
|
||||||
|
if !version.isEmpty {
|
||||||
|
self.firmwareversion = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnectedControllers() -> [Controller] {
|
||||||
|
|
||||||
|
|
||||||
|
guard let jsonPtr = get_game_controllers() else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the unmanaged memory (C string) to a Swift String
|
||||||
|
let jsonString = String(cString: jsonPtr)
|
||||||
|
|
||||||
|
var controllers: [Controller] = []
|
||||||
|
|
||||||
|
// Splitting the string by newline
|
||||||
|
let lines = jsonString.components(separatedBy: "\n")
|
||||||
|
|
||||||
|
// Parsing each line
|
||||||
|
for line in lines {
|
||||||
|
if line.contains(":") {
|
||||||
|
let parts = line.components(separatedBy: ":")
|
||||||
|
if parts.count == 2 {
|
||||||
|
let id = parts[0].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let name = parts[1].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
controllers.append(Controller(id: id, name: name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return controllers
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFirmware() {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
|
||||||
|
let documentsfolder = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
|
||||||
|
|
||||||
|
let bisFolder = documentsfolder.appendingPathComponent("bis")
|
||||||
|
let systemFolder = bisFolder.appendingPathComponent("system")
|
||||||
|
let contentsFolder = systemFolder.appendingPathComponent("Contents")
|
||||||
|
let registeredFolder = contentsFolder.appendingPathComponent("registered").path
|
||||||
|
|
||||||
|
|
||||||
|
do {
|
||||||
|
if fileManager.fileExists(atPath: registeredFolder) {
|
||||||
|
try fileManager.removeItem(atPath: registeredFolder)
|
||||||
|
print("Folder removed successfully.")
|
||||||
|
let version = fetchFirmwareVersion()
|
||||||
|
|
||||||
|
if version.isEmpty {
|
||||||
|
self.firmwareversion = "0"
|
||||||
|
} else {
|
||||||
|
print("Firmware eeeeee \(version)")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print("Folder does not exist.")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error removing folder: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static func log(_ message: String) {
|
||||||
|
print("[Ryujinx] \(message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
15
src/MeloNX/MeloNX/App/Core/Ryujinx/RyujinxError.swift
Normal file
15
src/MeloNX/MeloNX/App/Core/Ryujinx/RyujinxError.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// RyujinxError.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum RyujinxError: Error {
|
||||||
|
case libraryLoadError
|
||||||
|
case executionError(code: Int32)
|
||||||
|
case alreadyRunning
|
||||||
|
case notRunning
|
||||||
|
}
|
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 |
@ -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/Dynamic Libraries/libavcodec.dylib
Executable file
BIN
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libavcodec.dylib
Executable file
Binary file not shown.
BIN
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libavutil.dylib
Executable file
BIN
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libavutil.dylib
Executable file
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.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user