forked from MeloNX/MeloNX
Compare commits
No commits in common. "Message-Bridge" and "XC-ios-ht" have entirely different histories.
Message-Br
...
XC-ios-ht
64
.gitignore
vendored
64
.gitignore
vendored
@ -176,67 +176,3 @@ PublishProfiles/
|
|||||||
# Glade backup files
|
# Glade backup files
|
||||||
*.glade~
|
*.glade~
|
||||||
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib
|
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib
|
||||||
|
|
||||||
# SWIFT GITIGNORE
|
|
||||||
# Xcode
|
|
||||||
#
|
|
||||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
|
||||||
|
|
||||||
## User settings
|
|
||||||
xcuserdata/
|
|
||||||
|
|
||||||
## Obj-C/Swift specific
|
|
||||||
*.hmap
|
|
||||||
|
|
||||||
## App packaging
|
|
||||||
*.ipa
|
|
||||||
*.dSYM.zip
|
|
||||||
*.dSYM
|
|
||||||
|
|
||||||
## Playgrounds
|
|
||||||
timeline.xctimeline
|
|
||||||
playground.xcworkspace
|
|
||||||
|
|
||||||
# Swift Package Manager
|
|
||||||
#
|
|
||||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
|
||||||
# Packages/
|
|
||||||
# Package.pins
|
|
||||||
# Package.resolved
|
|
||||||
# *.xcodeproj
|
|
||||||
#
|
|
||||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
|
||||||
# hence it is not needed unless you have added a package configuration file to your project
|
|
||||||
# .swiftpm
|
|
||||||
|
|
||||||
.build/
|
|
||||||
|
|
||||||
# CocoaPods
|
|
||||||
#
|
|
||||||
# We recommend against adding the Pods directory to your .gitignore. However
|
|
||||||
# you should judge for yourself, the pros and cons are mentioned at:
|
|
||||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
||||||
#
|
|
||||||
# Pods/
|
|
||||||
#
|
|
||||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
|
||||||
# *.xcworkspace
|
|
||||||
|
|
||||||
# Carthage
|
|
||||||
#
|
|
||||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
|
||||||
# Carthage/Checkouts
|
|
||||||
|
|
||||||
Carthage/Build/
|
|
||||||
|
|
||||||
# fastlane
|
|
||||||
#
|
|
||||||
# It is recommended to not store the screenshots in the git repo.
|
|
||||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
|
||||||
# For more information about the recommended setup visit:
|
|
||||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
|
||||||
|
|
||||||
fastlane/report.xml
|
|
||||||
fastlane/Preview.html
|
|
||||||
fastlane/screenshots/**/*.png
|
|
||||||
fastlane/test_output
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
Before you begin, ensure you have the following installed:
|
Before you begin, ensure you have the following installed:
|
||||||
|
|
||||||
- [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
- [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
|
||||||
- [**Xcode**](https://apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12$0)
|
|
||||||
- A Mac running **macOS**
|
- A Mac running **macOS**
|
||||||
|
|
||||||
## Compilation Steps
|
## Compilation Steps
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
|
|
||||||
# Compatibility
|
# Compatibility
|
||||||
|
|
||||||
MeloNX works on iPhone XS/XR and later and iPad 8th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>.
|
MeloNX works on iPhone X and later and iPad 7th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
- MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but will have issues or may not work at all.
|
- MeloNX is made for iOS 17+, iOS 15 - 16 is supported but will have issues.
|
||||||
- MeloNX needs Xcode or a Paid Apple Developer Account. SideStore support may come soon (SideStore Side Issue)
|
- MeloNX needs Xcode or a Paid Apple Developer Account. SideStore support may come soon (SideStore Side Issue)
|
||||||
- MeloNX needs JIT
|
- MeloNX needs JIT
|
||||||
- Recommended Device: iPhone 15 Pro or newer.
|
- Recommended Device: iPhone 15 Pro or newer.
|
||||||
@ -60,8 +60,6 @@ If having Issues installing firmware (Make sure your Keys are installed first)
|
|||||||
|
|
||||||
### Xcode
|
### Xcode
|
||||||
|
|
||||||
**NOTE: These Xcode builds are nightly and may have unfinished features.**
|
|
||||||
|
|
||||||
1. **Compile Guide**
|
1. **Compile Guide**
|
||||||
- Visit the [guide here](https://git.743378673.xyz/MeloNX/MeloNX/src/branch/XC-ios-ht/Compile.md).
|
- Visit the [guide here](https://git.743378673.xyz/MeloNX/MeloNX/src/branch/XC-ios-ht/Compile.md).
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ namespace ARMeilleure.Memory
|
|||||||
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
||||||
|
|
||||||
public IJitMemoryBlock Block { get; }
|
public IJitMemoryBlock Block { get; }
|
||||||
public IJitMemoryAllocator Allocator { get; }
|
|
||||||
|
|
||||||
public IntPtr Pointer => Block.Pointer;
|
public IntPtr Pointer => Block.Pointer;
|
||||||
|
|
||||||
@ -22,7 +21,6 @@ namespace ARMeilleure.Memory
|
|||||||
granularity = DefaultGranularity;
|
granularity = DefaultGranularity;
|
||||||
}
|
}
|
||||||
|
|
||||||
Allocator = allocator;
|
|
||||||
Block = allocator.Reserve(maxSize);
|
Block = allocator.Reserve(maxSize);
|
||||||
_maxSize = maxSize;
|
_maxSize = maxSize;
|
||||||
_sizeGranularity = granularity;
|
_sizeGranularity = granularity;
|
||||||
|
@ -3,7 +3,6 @@ using ARMeilleure.CodeGen.Unwinding;
|
|||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.Native;
|
using ARMeilleure.Native;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -19,7 +18,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
private static readonly int _pageMask = _pageSize - 4;
|
private static readonly int _pageMask = _pageSize - 4;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 128 * 1024 * 1024;
|
private const int CacheSize = 1024 * 1024 * 1024;
|
||||||
private const int CacheSizeIOS = 128 * 1024 * 1024;
|
private const int CacheSizeIOS = 128 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
private static ReservedRegion _jitRegion;
|
||||||
@ -32,10 +31,6 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
private static readonly object _lock = new();
|
private static readonly object _lock = new();
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
|
|
||||||
private static readonly List<ReservedRegion> _jitRegions = new();
|
|
||||||
|
|
||||||
private static int _activeRegionIndex = 0;
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
|
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
|
||||||
@ -54,11 +49,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstRegion = new ReservedRegion(allocator, CacheSize);
|
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
|
||||||
|
|
||||||
|
|
||||||
_jitRegions.Add(firstRegion);
|
|
||||||
_activeRegionIndex = 0;
|
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
|
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
@ -69,9 +60,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
JitUnwindWindows.InstallFunctionTableHandler(
|
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
|
||||||
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@ -84,9 +73,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
while (_deferredRxProtect.TryDequeue(out var result))
|
while (_deferredRxProtect.TryDequeue(out var result))
|
||||||
{
|
{
|
||||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
ReprotectAsExecutable(result.funcOffset, result.length);
|
||||||
|
|
||||||
ReprotectAsExecutable(targetRegion, result.funcOffset, result.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,14 +87,21 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
int funcOffset = Allocate(code.Length, deferProtect);
|
int funcOffset = Allocate(code.Length, deferProtect);
|
||||||
|
|
||||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
|
||||||
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
|
|
||||||
|
|
||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
if (deferProtect)
|
||||||
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
{
|
||||||
|
_deferredRxProtect.Enqueue((funcOffset, code.Length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReprotectAsExecutable(funcOffset, code.Length);
|
||||||
|
|
||||||
|
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@ -121,9 +115,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
ReprotectAsWritable(funcOffset, code.Length);
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
ReprotectAsExecutable(funcOffset, code.Length);
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@ -145,50 +139,41 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
// return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var region in _jitRegions)
|
Debug.Assert(_initialized);
|
||||||
|
|
||||||
|
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||||
|
|
||||||
|
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||||
{
|
{
|
||||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
_cacheEntries.RemoveAt(entryIndex);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
|
||||||
|
|
||||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
|
||||||
{
|
|
||||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
|
||||||
_cacheEntries.RemoveAt(entryIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
private static void ReprotectAsWritable(int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
private static void ReprotectAsExecutable(int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int Allocate(int codeSize, bool deferProtect = false)
|
private static int Allocate(int codeSize, bool deferProtect = false)
|
||||||
@ -202,35 +187,20 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
alignment = 0x4000;
|
alignment = 0x4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
||||||
{
|
|
||||||
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
|
||||||
|
|
||||||
if (allocOffset >= 0)
|
//DEBUG: Show JIT Memory Allocation
|
||||||
{
|
|
||||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
|
||||||
_activeRegionIndex = i;
|
|
||||||
return allocOffset;
|
if (allocOffset < 0)
|
||||||
}
|
{
|
||||||
|
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int exhaustedRegion = _activeRegionIndex;
|
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||||
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
|
|
||||||
_jitRegions.Add(newRegion);
|
|
||||||
_activeRegionIndex = _jitRegions.Count - 1;
|
|
||||||
|
|
||||||
int newRegionNumber = _activeRegionIndex;
|
return allocOffset;
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
|
||||||
|
|
||||||
int allocOffsetNew = _cacheAllocator.Allocate(ref codeSize, alignment);
|
|
||||||
if (allocOffsetNew < 0)
|
|
||||||
{
|
|
||||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
|
||||||
}
|
|
||||||
|
|
||||||
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
|
||||||
return allocOffsetNew;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int AlignCodeSize(int codeSize, bool deferProtect = false)
|
private static int AlignCodeSize(int codeSize, bool deferProtect = false)
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
||||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@ -83,7 +82,7 @@
|
|||||||
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
||||||
5650564A2D2A758600C8BB1E /* dotnet.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = dotnet.xcconfig; sourceTree = "<group>"; };
|
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = dotnet.xcconfig.example; sourceTree = "<group>"; };
|
||||||
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -108,10 +107,6 @@
|
|||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
|
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
|
||||||
CodeSignOnCopy,
|
CodeSignOnCopy,
|
||||||
);
|
);
|
||||||
"Dependencies/Dynamic Libraries/RyujinxBridge.framework" = (
|
|
||||||
CodeSignOnCopy,
|
|
||||||
RemoveHeadersOnCopy,
|
|
||||||
);
|
|
||||||
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework" = (
|
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework" = (
|
||||||
CodeSignOnCopy,
|
CodeSignOnCopy,
|
||||||
RemoveHeadersOnCopy,
|
RemoveHeadersOnCopy,
|
||||||
@ -173,7 +168,6 @@
|
|||||||
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
"Dependencies/Dynamic Libraries/libavutil.dylib",
|
||||||
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
|
||||||
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
|
||||||
"Dependencies/Dynamic Libraries/RyujinxBridge.framework",
|
|
||||||
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework",
|
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework",
|
||||||
Dependencies/XCFrameworks/libavcodec.xcframework,
|
Dependencies/XCFrameworks/libavcodec.xcframework,
|
||||||
Dependencies/XCFrameworks/libavfilter.xcframework,
|
Dependencies/XCFrameworks/libavfilter.xcframework,
|
||||||
@ -203,7 +197,6 @@
|
|||||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
||||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -227,7 +220,7 @@
|
|||||||
4E80A9842CD6F54500029585 = {
|
4E80A9842CD6F54500029585 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5650564A2D2A758600C8BB1E /* dotnet.xcconfig */,
|
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */,
|
||||||
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
||||||
@ -265,7 +258,7 @@
|
|||||||
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
|
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
);
|
);
|
||||||
buildToolPath = /usr/local/share/dotnet/dotnet;
|
buildToolPath = "$(DOTNET_PATH)";
|
||||||
buildWorkingDirectory = "$(SRCROOT)/../..";
|
buildWorkingDirectory = "$(SRCROOT)/../..";
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
@ -638,7 +631,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = D59DHVRS87;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = NO;
|
ENABLE_TESTABILITY = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@ -663,27 +656,10 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
|
||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||||
@ -734,41 +710,9 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(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.3.0;
|
MARKETING_VERSION = 1.1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
@ -786,7 +730,7 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = D59DHVRS87;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@ -811,27 +755,10 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = fast;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
|
||||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||||
@ -882,41 +809,9 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(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.3.0;
|
MARKETING_VERSION = 1.1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.xitrix.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h";
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -1,116 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Bucket
|
|
||||||
uuid = "56EBE536-1F4C-4BCE-853B-D3E000BE57CD"
|
|
||||||
type = "1"
|
|
||||||
version = "2.0">
|
|
||||||
<Breakpoints>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "71A20387-E5D5-42A9-99A1-7D1B6BEB6A04"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "Yes"
|
|
||||||
symbolName = "UIApplicationMain"
|
|
||||||
moduleName = "">
|
|
||||||
<Actions>
|
|
||||||
<BreakpointActionProxy
|
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
|
||||||
<ActionContent
|
|
||||||
consoleCommand = "process handle SIGUSR1 -n true -p true -s false">
|
|
||||||
</ActionContent>
|
|
||||||
</BreakpointActionProxy>
|
|
||||||
</Actions>
|
|
||||||
<Locations>
|
|
||||||
<Location
|
|
||||||
uuid = "71A20387-E5D5-42A9-99A1-7D1B6BEB6A04 - dc2f8c5e001177f0"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
symbolName = "UIApplicationMain"
|
|
||||||
moduleName = "UIKitCore"
|
|
||||||
usesParentBreakpointCondition = "Yes">
|
|
||||||
</Location>
|
|
||||||
</Locations>
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "2A095BA1-FC06-4890-9989-2C4A83A0F028"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "Yes"
|
|
||||||
symbolName = "UIApplicationMain"
|
|
||||||
moduleName = "">
|
|
||||||
<Actions>
|
|
||||||
<BreakpointActionProxy
|
|
||||||
ActionExtensionID = "Xcode.BreakpointAction.DebuggerCommand">
|
|
||||||
<ActionContent
|
|
||||||
consoleCommand = "process handle SIGBUS -n true -p true -s false">
|
|
||||||
</ActionContent>
|
|
||||||
</BreakpointActionProxy>
|
|
||||||
</Actions>
|
|
||||||
<Locations>
|
|
||||||
<Location
|
|
||||||
uuid = "2A095BA1-FC06-4890-9989-2C4A83A0F028 - dc2f8c5e001177f0"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
symbolName = "UIApplicationMain"
|
|
||||||
moduleName = "UIKitCore"
|
|
||||||
usesParentBreakpointCondition = "Yes">
|
|
||||||
</Location>
|
|
||||||
</Locations>
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "A363121B-631E-4ADE-8A05-D3D9538EE31D"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "MeloNX/App/Core/Ryujinx/RyujinxBridge.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "43"
|
|
||||||
endingLineNumber = "43"
|
|
||||||
landmarkName = "parseMessage(_:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "482C1F2E-138F-49D2-A590-8B98AC9B790E"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "MeloNX/App/Core/Ryujinx/RyujinxBridge.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "32"
|
|
||||||
endingLineNumber = "32"
|
|
||||||
landmarkName = "parseMessage(_:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "FF32B294-2681-4A6E-9DFA-0D13DA242EAF"
|
|
||||||
shouldBeEnabled = "No"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "MeloNX/App/Core/Ryujinx/RyujinxBridge.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "30"
|
|
||||||
endingLineNumber = "30"
|
|
||||||
landmarkName = "parseMessage(_:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
</Breakpoints>
|
|
||||||
</Bucket>
|
|
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>MeloNX.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -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>3</integer>
|
||||||
|
</dict>
|
||||||
|
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>4</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>
|
@ -1,58 +0,0 @@
|
|||||||
//
|
|
||||||
// EntitlementChecker.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 15/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Security
|
|
||||||
|
|
||||||
typealias SecTaskRef = OpaquePointer
|
|
||||||
|
|
||||||
@_silgen_name("SecTaskCopyValueForEntitlement")
|
|
||||||
func SecTaskCopyValueForEntitlement(
|
|
||||||
_ task: SecTaskRef,
|
|
||||||
_ entitlement: NSString,
|
|
||||||
_ error: NSErrorPointer
|
|
||||||
) -> CFTypeRef?
|
|
||||||
|
|
||||||
@_silgen_name("SecTaskCreateFromSelf")
|
|
||||||
func SecTaskCreateFromSelf(
|
|
||||||
_ allocator: CFAllocator?
|
|
||||||
) -> SecTaskRef?
|
|
||||||
|
|
||||||
@_silgen_name("SecTaskCopyValuesForEntitlements")
|
|
||||||
func SecTaskCopyValuesForEntitlements(
|
|
||||||
_ task: SecTaskRef,
|
|
||||||
_ entitlements: CFArray,
|
|
||||||
_ error: UnsafeMutablePointer<Unmanaged<CFError>?>?
|
|
||||||
) -> CFDictionary?
|
|
||||||
|
|
||||||
func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
|
|
||||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
|
||||||
print("Failed to create SecTask")
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let entitlements = SecTaskCopyValuesForEntitlements(task, ents as CFArray, nil) else {
|
|
||||||
print("Failed to get entitlements")
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return (entitlements as? [String: Any]) ?? [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAppEntitlement(_ ent: String) -> Bool {
|
|
||||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
|
||||||
print("Failed to create SecTask")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let entitlements = SecTaskCopyValueForEntitlement(task, ent as NSString, nil) else {
|
|
||||||
print("Failed to get entitlements")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return entitlements.boolValue != nil && entitlements.boolValue
|
|
||||||
}
|
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_syswm.h>
|
#include <SDL2/SDL_syswm.h>
|
||||||
|
#import "utils.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -29,21 +31,8 @@ struct GameInfo {
|
|||||||
unsigned int ImageSize;
|
unsigned int ImageSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DlcNcaListItem {
|
|
||||||
char Path[256];
|
|
||||||
unsigned long TitleId;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DlcNcaList {
|
|
||||||
bool success;
|
|
||||||
unsigned int size;
|
|
||||||
struct DlcNcaListItem* items;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern struct GameInfo get_game_info(int, char*);
|
extern struct GameInfo get_game_info(int, char*);
|
||||||
|
|
||||||
extern struct DlcNcaList get_dlc_nca_list(const char* titleIdPtr, const char* pathPtr);
|
|
||||||
|
|
||||||
void install_firmware(const char* inputPtr);
|
void install_firmware(const char* inputPtr);
|
||||||
|
|
||||||
char* installed_firmware_version();
|
char* installed_firmware_version();
|
||||||
|
@ -5,49 +5,15 @@
|
|||||||
// Created by Stossy11 on 10/02/2025.
|
// Created by Stossy11 on 10/02/2025.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
|
|
||||||
var region: vm_address_t = vm_address_t(UInt(bitPattern: address))
|
|
||||||
var regionSize: vm_size_t = 0
|
|
||||||
var info = vm_region_basic_info_64()
|
|
||||||
var infoCount = mach_msg_type_number_t(MemoryLayout<vm_region_basic_info_64>.size / MemoryLayout<integer_t>.size)
|
|
||||||
var objectName: mach_port_t = UInt32(MACH_PORT_NULL)
|
|
||||||
|
|
||||||
let result = withUnsafeMutablePointer(to: &info) {
|
|
||||||
$0.withMemoryRebound(to: integer_t.self, capacity: Int(infoCount)) {
|
|
||||||
vm_region_64(mach_task_self_, ®ion, ®ionSize, VM_REGION_BASIC_INFO_64, $0, &infoCount, &objectName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != KERN_SUCCESS {
|
|
||||||
print("Failed to reach \(address)")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return info.protection & VM_PROT_EXECUTE != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isJITEnabled() -> Bool {
|
func isJITEnabled() -> Bool {
|
||||||
let pageSize = sysconf(_SC_PAGESIZE)
|
var flags: Int = 0
|
||||||
let code: [UInt32] = [0x52800540, 0xD65F03C0]
|
|
||||||
|
|
||||||
guard let jitMemory = mmap(nil, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0), jitMemory != MAP_FAILED else {
|
csops(getpid(), 0, &flags, sizeof(flags))
|
||||||
return false
|
return (Int32(flags) & CS_DEBUGGED) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
defer {
|
func sizeof<T>(_ value: T) -> Int {
|
||||||
munmap(jitMemory, pageSize)
|
return MemoryLayout<T>.size
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
memcpy(jitMemory, code, code.count)
|
|
||||||
|
|
||||||
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let checkMem = checkMemoryPermissions(at: jitMemory)
|
|
||||||
|
|
||||||
return checkMem
|
|
||||||
}
|
}
|
||||||
|
27
src/MeloNX/MeloNX/App/Core/JIT/utils.h
Normal file
27
src/MeloNX/MeloNX/App/Core/JIT/utils.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#if __has_feature(modules)
|
||||||
|
@import UIKit;
|
||||||
|
@import Foundation;
|
||||||
|
#else
|
||||||
|
#import "UIKit/UIKit.h"
|
||||||
|
#import "Foundation/Foundation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DISPATCH_ASYNC_START dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
#define DISPATCH_ASYNC_CLOSE });
|
||||||
|
|
||||||
|
#define PT_TRACE_ME 0
|
||||||
|
extern int ptrace(int, pid_t, caddr_t, int);
|
||||||
|
|
||||||
|
#define CS_DEBUGGED 0x10000000
|
||||||
|
extern int csops(
|
||||||
|
pid_t pid,
|
||||||
|
unsigned int ops,
|
||||||
|
void *useraddr,
|
||||||
|
size_t usersize
|
||||||
|
);
|
||||||
|
|
||||||
|
extern BOOL getEntitlementValue(NSString *key);
|
||||||
|
extern BOOL isJITEnabled(void);
|
||||||
|
|
||||||
|
#define DLOG(format, ...) ShowAlert(@"DEBUG", [NSString stringWithFormat:@"\n %s [Line %d] \n %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:format, ##__VA_ARGS__]])
|
||||||
|
void ShowAlert(NSString* title, NSString* message, _Bool* showok);
|
82
src/MeloNX/MeloNX/App/Core/JIT/utils.m
Normal file
82
src/MeloNX/MeloNX/App/Core/JIT/utils.m
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#import "utils.h"
|
||||||
|
|
||||||
|
typedef struct __SecTask * SecTaskRef;
|
||||||
|
extern CFTypeRef SecTaskCopyValueForEntitlement(
|
||||||
|
SecTaskRef task,
|
||||||
|
NSString* entitlement,
|
||||||
|
CFErrorRef _Nullable *error
|
||||||
|
)
|
||||||
|
__attribute__((weak_import));
|
||||||
|
|
||||||
|
extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator)
|
||||||
|
__attribute__((weak_import));
|
||||||
|
|
||||||
|
BOOL getEntitlementValue(NSString *key)
|
||||||
|
{
|
||||||
|
if (SecTaskCreateFromSelf == NULL || SecTaskCopyValueForEntitlement == NULL)
|
||||||
|
return NO;
|
||||||
|
SecTaskRef sec_task = SecTaskCreateFromSelf(NULL);
|
||||||
|
if(!sec_task) return NO;
|
||||||
|
CFTypeRef value = SecTaskCopyValueForEntitlement(sec_task, key, nil);
|
||||||
|
if (value != nil)
|
||||||
|
{
|
||||||
|
CFRelease(value);
|
||||||
|
}
|
||||||
|
CFRelease(sec_task);
|
||||||
|
return value != nil && [(__bridge id)value boolValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL isJITEnabled(void)
|
||||||
|
{
|
||||||
|
if (getEntitlementValue(@"dynamic-codesigning"))
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags;
|
||||||
|
csops(getpid(), 0, &flags, sizeof(flags));
|
||||||
|
return (flags & CS_DEBUGGED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowAlert(NSString* title, NSString* message, _Bool* showok)
|
||||||
|
{
|
||||||
|
DISPATCH_ASYNC_START
|
||||||
|
UIWindow* mainWindow = [[UIApplication sharedApplication] windows].lastObject;
|
||||||
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
|
||||||
|
message:message
|
||||||
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
|
if (showok) {
|
||||||
|
[alert addAction:[UIAlertAction actionWithTitle:@"ok!"
|
||||||
|
style:UIAlertActionStyleDefault
|
||||||
|
handler:nil]];
|
||||||
|
}
|
||||||
|
[mainWindow.rootViewController presentViewController:alert
|
||||||
|
animated:true
|
||||||
|
completion:nil];
|
||||||
|
DISPATCH_ASYNC_CLOSE
|
||||||
|
}
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
__attribute__((constructor)) static void entry(int argc, char **argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
|
||||||
|
NSLog(@"Entitlement Does Exist");
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
[defaults setBool:YES forKey:@"increased-memory-limit"];
|
||||||
|
[defaults synchronize]; // Ensure the value is saved immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getEntitlementValue(@"com.apple.developer.kernel.increased-debugging-memory-limit")) {
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
[defaults setBool:YES forKey:@"increased-debugging-memory-limit"];
|
||||||
|
[defaults synchronize]; // Ensure the value is saved immediately
|
||||||
|
}
|
||||||
|
if (getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) {
|
||||||
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
|
[defaults setBool:YES forKey:@"extended-virtual-addressing"];
|
||||||
|
[defaults synchronize]; // Ensure the value is saved immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,237 +0,0 @@
|
|||||||
//
|
|
||||||
// NativeController.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by XITRIX on 15/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import CoreHaptics
|
|
||||||
import GameController
|
|
||||||
|
|
||||||
class NativeController: Hashable {
|
|
||||||
private var instanceID: SDL_JoystickID = -1
|
|
||||||
private var controller: OpaquePointer?
|
|
||||||
private var nativeController: GCController
|
|
||||||
private let controllerHaptics: CHHapticEngine?
|
|
||||||
|
|
||||||
public var controllername: String { "GC - \(nativeController.vendorName ?? "Unknown")" }
|
|
||||||
|
|
||||||
init(_ controller: GCController) {
|
|
||||||
nativeController = controller
|
|
||||||
controllerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
|
|
||||||
try? controllerHaptics?.start()
|
|
||||||
setupHandheldController()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupHandheldController() {
|
|
||||||
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
|
|
||||||
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
|
|
||||||
}
|
|
||||||
|
|
||||||
var joystickDesc = SDL_VirtualJoystickDesc(
|
|
||||||
version: UInt16(SDL_VIRTUAL_JOYSTICK_DESC_VERSION),
|
|
||||||
type: Uint16(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue),
|
|
||||||
naxes: 6,
|
|
||||||
nbuttons: 15,
|
|
||||||
nhats: 1,
|
|
||||||
vendor_id: 0,
|
|
||||||
product_id: 0,
|
|
||||||
padding: 0,
|
|
||||||
button_mask: 0,
|
|
||||||
axis_mask: 0,
|
|
||||||
name: (controllername as NSString).utf8String,
|
|
||||||
userdata: Unmanaged.passUnretained(self).toOpaque(),
|
|
||||||
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)")
|
|
||||||
guard let userdata else { return 0 }
|
|
||||||
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
|
|
||||||
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq), engine: _self.controllerHaptics)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
guard let gamepad = nativeController.extendedGamepad
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.buttonA, for: .B)
|
|
||||||
setupButtonChangeListener(gamepad.buttonB, for: .A)
|
|
||||||
setupButtonChangeListener(gamepad.buttonX, for: .Y)
|
|
||||||
setupButtonChangeListener(gamepad.buttonY, for: .X)
|
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
|
|
||||||
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
|
|
||||||
setupButtonChangeListener(gamepad.dpad.left, for: .dPadLeft)
|
|
||||||
setupButtonChangeListener(gamepad.dpad.right, for: .dPadRight)
|
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.leftShoulder, for: .leftShoulder)
|
|
||||||
setupButtonChangeListener(gamepad.rightShoulder, for: .rightShoulder)
|
|
||||||
gamepad.leftThumbstickButton.map { setupButtonChangeListener($0, for: .leftStick) }
|
|
||||||
gamepad.rightThumbstickButton.map { setupButtonChangeListener($0, for: .rightStick) }
|
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.buttonMenu, for: .start)
|
|
||||||
gamepad.buttonOptions.map { setupButtonChangeListener($0, for: .back) }
|
|
||||||
|
|
||||||
setupStickChangeListener(gamepad.leftThumbstick, for: .left)
|
|
||||||
setupStickChangeListener(gamepad.rightThumbstick, for: .right)
|
|
||||||
|
|
||||||
setupTriggerChangeListener(gamepad.leftTrigger, for: .left)
|
|
||||||
setupTriggerChangeListener(gamepad.rightTrigger, for: .right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupButtonChangeListener(_ button: GCControllerButtonInput, for key: VirtualControllerButton) {
|
|
||||||
button.valueChangedHandler = { [unowned self] _, _, pressed in
|
|
||||||
setButtonState(pressed ? 1 : 0, for: key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupStickChangeListener(_ button: GCControllerDirectionPad, for key: ThumbstickType) {
|
|
||||||
button.valueChangedHandler = { [unowned self] _, xValue, yValue in
|
|
||||||
let scaledX = Sint16(xValue * 32767.0)
|
|
||||||
let scaledY = -Sint16(yValue * 32767.0)
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case .left:
|
|
||||||
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTX.rawValue))
|
|
||||||
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTY.rawValue))
|
|
||||||
case .right:
|
|
||||||
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
|
||||||
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTY.rawValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTriggerChangeListener(_ button: GCControllerButtonInput, for key: ThumbstickType) {
|
|
||||||
button.valueChangedHandler = { [unowned self] _, value, pressed in
|
|
||||||
// print("Value: \(value), Is pressed: \(pressed)")
|
|
||||||
let axis: SDL_GameControllerAxis = (key == .left) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
|
||||||
let scaledValue = Sint16(value * 32767.0)
|
|
||||||
updateAxisValue(value: scaledValue, forAxis: axis)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
SDL_JoystickDetachVirtual(instanceID)
|
|
||||||
SDL_GameControllerClose(controller)
|
|
||||||
self.controller = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(nativeController)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func == (lhs: NativeController, rhs: NativeController) -> Bool {
|
|
||||||
lhs.nativeController == rhs.nativeController
|
|
||||||
}
|
|
||||||
}
|
|
@ -78,7 +78,7 @@ class VirtualController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func rumble(lowFreq: Float, highFreq: Float, engine: CHHapticEngine? = nil) {
|
static func rumble(lowFreq: Float, highFreq: Float) {
|
||||||
do {
|
do {
|
||||||
// Low-frequency haptic pattern
|
// Low-frequency haptic pattern
|
||||||
let lowFreqPattern = try CHHapticPattern(events: [
|
let lowFreqPattern = try CHHapticPattern(events: [
|
||||||
@ -96,23 +96,9 @@ class VirtualController {
|
|||||||
], relativeTime: 0.2, duration: 0.2)
|
], relativeTime: 0.2, duration: 0.2)
|
||||||
], parameters: [])
|
], parameters: [])
|
||||||
|
|
||||||
// Mutable engine
|
// Create and start the haptic engine
|
||||||
var engine = engine
|
let engine = try CHHapticEngine()
|
||||||
|
try engine.start()
|
||||||
// If no engine passed, use device engine
|
|
||||||
if engine == nil {
|
|
||||||
// Create and start the haptic engine
|
|
||||||
if hapticEngine == nil {
|
|
||||||
hapticEngine = try CHHapticEngine()
|
|
||||||
try hapticEngine?.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
engine = hapticEngine
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let engine else {
|
|
||||||
return print("Error creating haptic patterns: hapticEngine is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and play the low-frequency player
|
// Create and play the low-frequency player
|
||||||
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
||||||
@ -127,8 +113,6 @@ class VirtualController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static var hapticEngine: CHHapticEngine?
|
|
||||||
|
|
||||||
|
|
||||||
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
|
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
|
||||||
guard controller != nil else { return }
|
guard controller != nil else { return }
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// AspectRatio.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 16/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public enum AspectRatio: String, Codable, CaseIterable {
|
|
||||||
case fixed4x3 = "Fixed4x3"
|
|
||||||
case fixed16x9 = "Fixed16x9"
|
|
||||||
case fixed16x10 = "Fixed16x10"
|
|
||||||
case fixed21x9 = "Fixed21x9"
|
|
||||||
case fixed32x9 = "Fixed32x9"
|
|
||||||
case stretched = "Stretched"
|
|
||||||
|
|
||||||
var displayName: String {
|
|
||||||
switch self {
|
|
||||||
case .fixed4x3: return "4:3"
|
|
||||||
case .fixed16x9: return "16:9 (Default)"
|
|
||||||
case .fixed16x10: return "16:10"
|
|
||||||
case .fixed21x9: return "21:9"
|
|
||||||
case .fixed32x9: return "32:9"
|
|
||||||
case .stretched: return "Stretched (Full Screen)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
//
|
|
||||||
// Language.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 16/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public enum SystemLanguage: String, Codable, CaseIterable {
|
|
||||||
case japanese = "Japanese"
|
|
||||||
case americanEnglish = "AmericanEnglish"
|
|
||||||
case french = "French"
|
|
||||||
case german = "German"
|
|
||||||
case italian = "Italian"
|
|
||||||
case spanish = "Spanish"
|
|
||||||
case chinese = "Chinese"
|
|
||||||
case korean = "Korean"
|
|
||||||
case dutch = "Dutch"
|
|
||||||
case portuguese = "Portuguese"
|
|
||||||
case russian = "Russian"
|
|
||||||
case taiwanese = "Taiwanese"
|
|
||||||
case britishEnglish = "BritishEnglish"
|
|
||||||
case canadianFrench = "CanadianFrench"
|
|
||||||
case latinAmericanSpanish = "LatinAmericanSpanish"
|
|
||||||
case simplifiedChinese = "SimplifiedChinese"
|
|
||||||
case traditionalChinese = "TraditionalChinese"
|
|
||||||
case brazilianPortuguese = "BrazilianPortuguese"
|
|
||||||
|
|
||||||
var displayName: String {
|
|
||||||
switch self {
|
|
||||||
case .japanese: return "Japanese"
|
|
||||||
case .americanEnglish: return "American English"
|
|
||||||
case .french: return "French"
|
|
||||||
case .german: return "German"
|
|
||||||
case .italian: return "Italian"
|
|
||||||
case .spanish: return "Spanish"
|
|
||||||
case .chinese: return "Chinese"
|
|
||||||
case .korean: return "Korean"
|
|
||||||
case .dutch: return "Dutch"
|
|
||||||
case .portuguese: return "Portuguese"
|
|
||||||
case .russian: return "Russian"
|
|
||||||
case .taiwanese: return "Taiwanese"
|
|
||||||
case .britishEnglish: return "British English"
|
|
||||||
case .canadianFrench: return "Canadian French"
|
|
||||||
case .latinAmericanSpanish: return "Latin American Spanish"
|
|
||||||
case .simplifiedChinese: return "Simplified Chinese"
|
|
||||||
case .traditionalChinese: return "Traditional Chinese"
|
|
||||||
case .brazilianPortuguese: return "Brazilian Portuguese"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
//
|
|
||||||
// Region.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 16/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public enum SystemRegionCode: String, Codable, CaseIterable {
|
|
||||||
case japan = "Japan"
|
|
||||||
case usa = "USA"
|
|
||||||
case europe = "Europe"
|
|
||||||
case australia = "Australia"
|
|
||||||
case china = "China"
|
|
||||||
case korea = "Korea"
|
|
||||||
case taiwan = "Taiwan"
|
|
||||||
|
|
||||||
var displayName: String {
|
|
||||||
switch self {
|
|
||||||
case .japan: return "Japan"
|
|
||||||
case .usa: return "United States"
|
|
||||||
case .europe: return "Europe"
|
|
||||||
case .australia: return "Australia"
|
|
||||||
case .china: return "China"
|
|
||||||
case .korea: return "Korea"
|
|
||||||
case .taiwan: return "Taiwan"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GameController
|
import GameController
|
||||||
import RyujinxBridge
|
|
||||||
|
|
||||||
struct Controller: Identifiable, Hashable {
|
struct Controller: Identifiable, Hashable {
|
||||||
var id: String
|
var id: String
|
||||||
@ -29,6 +28,26 @@ struct iOSNav<Content: View>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum AspectRatio: String, Codable, CaseIterable {
|
||||||
|
case fixed4x3 = "Fixed4x3"
|
||||||
|
case fixed16x9 = "Fixed16x9"
|
||||||
|
case fixed16x10 = "Fixed16x10"
|
||||||
|
case fixed21x9 = "Fixed21x9"
|
||||||
|
case fixed32x9 = "Fixed32x9"
|
||||||
|
case stretched = "Stretched"
|
||||||
|
|
||||||
|
var displayName: String {
|
||||||
|
switch self {
|
||||||
|
case .fixed4x3: return "4:3"
|
||||||
|
case .fixed16x9: return "16:9 (Default)"
|
||||||
|
case .fixed16x10: return "16:10"
|
||||||
|
case .fixed21x9: return "21:9"
|
||||||
|
case .fixed32x9: return "32:9"
|
||||||
|
case .stretched: return "Stretched (Full Screen)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Ryujinx {
|
class Ryujinx {
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
@ -41,8 +60,6 @@ class Ryujinx {
|
|||||||
@Published var emulationUIView = UIView()
|
@Published var emulationUIView = UIView()
|
||||||
@Published var games: [Game] = []
|
@Published var games: [Game] = []
|
||||||
|
|
||||||
@Published var defMLContentSize: CGFloat?
|
|
||||||
|
|
||||||
var shouldMetal: Bool {
|
var shouldMetal: Bool {
|
||||||
metalLayer == nil
|
metalLayer == nil
|
||||||
}
|
}
|
||||||
@ -50,13 +67,6 @@ class Ryujinx {
|
|||||||
static let shared = Ryujinx()
|
static let shared = Ryujinx()
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
messageDelegate = { messagePtr in
|
|
||||||
guard let messagePtr else { return }
|
|
||||||
let message = String(cString: messagePtr)
|
|
||||||
RyujinxBridgeHelper.parseMessage(message)
|
|
||||||
print("Message: \(message)")
|
|
||||||
}
|
|
||||||
|
|
||||||
self.games = loadGames()
|
self.games = loadGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +93,6 @@ class Ryujinx {
|
|||||||
var dfsIntegrityChecks: Bool
|
var dfsIntegrityChecks: Bool
|
||||||
var disablePTC: Bool
|
var disablePTC: Bool
|
||||||
var disablevsync: Bool
|
var disablevsync: Bool
|
||||||
var language: SystemLanguage
|
|
||||||
var regioncode: SystemRegionCode
|
|
||||||
var handHeldController: Bool
|
|
||||||
|
|
||||||
|
|
||||||
init(gamepath: String,
|
init(gamepath: String,
|
||||||
@ -109,10 +116,7 @@ class Ryujinx {
|
|||||||
expandRam: Bool = false,
|
expandRam: Bool = false,
|
||||||
dfsIntegrityChecks: Bool = false,
|
dfsIntegrityChecks: Bool = false,
|
||||||
disablePTC: Bool = false,
|
disablePTC: Bool = false,
|
||||||
disablevsync: Bool = false,
|
disablevsync: Bool = false
|
||||||
language: SystemLanguage = .americanEnglish,
|
|
||||||
regioncode: SystemRegionCode = .usa,
|
|
||||||
handHeldController: Bool = false
|
|
||||||
) {
|
) {
|
||||||
self.gamepath = gamepath
|
self.gamepath = gamepath
|
||||||
self.inputids = inputids
|
self.inputids = inputids
|
||||||
@ -136,9 +140,6 @@ class Ryujinx {
|
|||||||
self.dfsIntegrityChecks = dfsIntegrityChecks
|
self.dfsIntegrityChecks = dfsIntegrityChecks
|
||||||
self.disablePTC = disablePTC
|
self.disablePTC = disablePTC
|
||||||
self.disablevsync = disablevsync
|
self.disablevsync = disablevsync
|
||||||
self.language = language
|
|
||||||
self.regioncode = regioncode
|
|
||||||
self.handHeldController = handHeldController
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +234,7 @@ class Ryujinx {
|
|||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return games
|
return games
|
||||||
} catch {
|
} catch {
|
||||||
print("Error loading games from roms folder: \(error)")
|
print("Error loading games from roms folder: \(error)")
|
||||||
@ -260,10 +261,6 @@ class Ryujinx {
|
|||||||
// We don't need this. Ryujinx should handle it fine :3
|
// We don't need this. Ryujinx should handle it fine :3
|
||||||
// this also causes crashes in some games :3
|
// this also causes crashes in some games :3
|
||||||
|
|
||||||
args.append(contentsOf: ["--system-language", config.language.rawValue])
|
|
||||||
|
|
||||||
args.append(contentsOf: ["--system-region", config.regioncode.rawValue])
|
|
||||||
|
|
||||||
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
|
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
|
||||||
|
|
||||||
if config.nintendoinput {
|
if config.nintendoinput {
|
||||||
@ -278,7 +275,7 @@ class Ryujinx {
|
|||||||
args.append("--disable-vsync")
|
args.append("--disable-vsync")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if config.hypervisor {
|
if config.hypervisor {
|
||||||
args.append("--use-hypervisor")
|
args.append("--use-hypervisor")
|
||||||
}
|
}
|
||||||
@ -297,8 +294,7 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.ignoreMissingServices {
|
if config.ignoreMissingServices {
|
||||||
// args.append(contentsOf: ["--ignore-missing-services"])
|
args.append(contentsOf: ["--ignore-missing-services", String(config.maxAnisotropy)])
|
||||||
args.append("--ignore-missing-services")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.maxAnisotropy != 0 {
|
if config.maxAnisotropy != 0 {
|
||||||
@ -321,25 +317,21 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.debuglogs {
|
if config.debuglogs {
|
||||||
args.append("--enable-debug-logs")
|
args.append(contentsOf: ["--enable-debug-logs"])
|
||||||
}
|
}
|
||||||
if config.tracelogs {
|
if config.tracelogs {
|
||||||
args.append("--enable-trace-logs")
|
args.append(contentsOf: ["--enable-trace-logs"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// List the input ids
|
// List the input ids
|
||||||
if config.listinputids {
|
if config.listinputids {
|
||||||
args.append("--list-inputs-ids")
|
args.append(contentsOf: ["--list-inputs-ids"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the input ids (limit to 8 (used to be 4) just in case)
|
// Append the input ids (limit to 4 just in case)
|
||||||
if !config.inputids.isEmpty {
|
if !config.inputids.isEmpty {
|
||||||
config.inputids.prefix(8).enumerated().forEach { index, inputId in
|
config.inputids.prefix(4).enumerated().forEach { index, inputId in
|
||||||
if config.handHeldController {
|
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
|
||||||
args.append(contentsOf: ["\(index == 0 ? "--input-id-handheld" : "--input-id-\(index + 1)")", inputId])
|
|
||||||
} else {
|
|
||||||
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,30 +372,7 @@ class Ryujinx {
|
|||||||
self.firmwareversion = version
|
self.firmwareversion = version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDlcNcaList(titleId: String, path: String) -> [DownloadableContentNca] {
|
|
||||||
guard let titleIdCString = titleId.cString(using: .utf8),
|
|
||||||
let pathCString = path.cString(using: .utf8)
|
|
||||||
else {
|
|
||||||
print("Invalid path")
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
let listPointer = get_dlc_nca_list(titleIdCString, pathCString)
|
|
||||||
print("DLC parcing success: \(listPointer.success)")
|
|
||||||
guard listPointer.success else { return [] }
|
|
||||||
|
|
||||||
let list = Array(UnsafeBufferPointer(start: listPointer.items, count: Int(listPointer.size)))
|
|
||||||
|
|
||||||
return list.map { item in
|
|
||||||
.init(fullPath: withUnsafePointer(to: item.Path) {
|
|
||||||
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
|
|
||||||
String(cString: $0)
|
|
||||||
}
|
|
||||||
}, titleId: item.TitleId, enabled: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
||||||
let guid = SDL_JoystickGetDeviceGUID(joystickIndex)
|
let guid = SDL_JoystickGetDeviceGUID(joystickIndex)
|
||||||
|
|
||||||
@ -492,26 +461,23 @@ class Ryujinx {
|
|||||||
|
|
||||||
|
|
||||||
func repeatuntilfindLayer() {
|
func repeatuntilfindLayer() {
|
||||||
Task { @MainActor in
|
DispatchQueue.global(qos: .background).async {
|
||||||
while self.metalLayer == nil {
|
while self.metalLayer == nil {
|
||||||
let layer = self.getMetalLayer(nil)
|
let layer = self.getMetalLayer(nil)
|
||||||
|
|
||||||
if layer != nil {
|
if layer != nil {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.metalLayer = layer
|
self.metalLayer = layer
|
||||||
}
|
}
|
||||||
self.metalLayer = layer
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.sleep(forTimeInterval: 0.1)
|
Thread.sleep(forTimeInterval: 0.1)
|
||||||
try await Task.sleep(nanoseconds: 100_000_000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
|
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
|
||||||
var window = window
|
var window = window
|
||||||
if window == nil {
|
if window == nil {
|
||||||
@ -521,6 +487,7 @@ class Ryujinx {
|
|||||||
var windowInfo = SDL_SysWMinfo()
|
var windowInfo = SDL_SysWMinfo()
|
||||||
SDL_GetWindowWMInfo(window, &windowInfo)
|
SDL_GetWindowWMInfo(window, &windowInfo)
|
||||||
|
|
||||||
|
|
||||||
guard let uiWindow = windowInfo.info.uikit.window,
|
guard let uiWindow = windowInfo.info.uikit.window,
|
||||||
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
|
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
|
||||||
print("Unable to get root view")
|
print("Unable to get root view")
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
//
|
|
||||||
// RyujinxBridge.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Daniil Vinogradov on 17/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
struct BridgeAlertMessage: Codable {
|
|
||||||
var title: String?
|
|
||||||
var type: String?
|
|
||||||
var metadata: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BridgePayload<T> {
|
|
||||||
var type: String
|
|
||||||
var model: T
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RyujinxBridgeHelper {
|
|
||||||
static func parseMessage(_ message: String) {
|
|
||||||
guard let data = message.data(using: .utf8),
|
|
||||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
||||||
let type = json["type"] as? String,
|
|
||||||
let model = json["model"],
|
|
||||||
let modelData = try? JSONSerialization.data(withJSONObject: model)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case "BridgeAlertMessage":
|
|
||||||
guard let model = try? JSONDecoder().decode(BridgeAlertMessage.self, from: modelData)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
// let alertVC = UIAlertController(title: model.title, message: model.type, preferredStyle: .alert)
|
|
||||||
// alertVC.addAction(.init(title: "OK", style: .cancel))
|
|
||||||
print("ALERT!!!! \(model.title) \(model.type) \(model.metadata)")
|
|
||||||
// UIApplication.shared.windows.first?.rootViewController?.present(alertVC, animated: true)
|
|
||||||
}
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
print("Type: \(type)")
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,7 @@ import SwiftUI
|
|||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
public struct Game: Identifiable, Equatable, Hashable {
|
public struct Game: Identifiable, Equatable, Hashable {
|
||||||
public var id: URL { fileURL }
|
public var id = UUID()
|
||||||
|
|
||||||
var containerFolder: URL
|
var containerFolder: URL
|
||||||
var fileType: UTType
|
var fileType: UTType
|
||||||
|
@ -26,7 +26,6 @@ struct ContentView: View {
|
|||||||
@State private var controllersList: [Controller] = []
|
@State private var controllersList: [Controller] = []
|
||||||
@State private var currentControllers: [Controller] = []
|
@State private var currentControllers: [Controller] = []
|
||||||
@State var onscreencontroller: Controller = Controller(id: "", name: "")
|
@State var onscreencontroller: Controller = Controller(id: "", name: "")
|
||||||
@State var nativeControllers: [GCController: NativeController] = [:]
|
|
||||||
@State private var isVirtualControllerActive: Bool = false
|
@State private var isVirtualControllerActive: Bool = false
|
||||||
@AppStorage("isVirtualController") var isVCA: Bool = true
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ struct ContentView: View {
|
|||||||
@AppStorage("quit") var quit: Bool = false
|
@AppStorage("quit") var quit: Bool = false
|
||||||
@State var quits: Bool = false
|
@State var quits: Bool = false
|
||||||
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
||||||
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = true
|
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
|
||||||
|
|
||||||
// Loading Animation
|
// Loading Animation
|
||||||
@State private var clumpOffset: CGFloat = -100
|
@State private var clumpOffset: CGFloat = -100
|
||||||
@ -51,23 +50,31 @@ struct ContentView: View {
|
|||||||
private let animationDuration: Double = 1.0
|
private let animationDuration: Double = 1.0
|
||||||
@State private var isAnimating = false
|
@State private var isAnimating = false
|
||||||
@State var isLoading = true
|
@State var isLoading = true
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
init() {
|
init() {
|
||||||
let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "")
|
let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "")
|
||||||
_config = State(initialValue: defaultConfig)
|
_config = State(initialValue: defaultConfig)
|
||||||
|
|
||||||
let defaultSettings: [MoltenVKSettings] = [ // Default MoltenVK Settings.
|
let defaultSettings: [MoltenVKSettings] = [
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
|
||||||
|
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2"),
|
||||||
|
// Metal Private API isn't needed and causes more stutters
|
||||||
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
|
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
|
||||||
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64)
|
// MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "128"),
|
// MVK_CONFIG_LOG_LEVEL
|
||||||
|
//MVK_DEBUG
|
||||||
|
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64 or 192 depending on what i decide)
|
||||||
|
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
|
||||||
]
|
]
|
||||||
|
|
||||||
_settings = State(initialValue: defaultSettings)
|
_settings = State(initialValue: defaultSettings)
|
||||||
|
|
||||||
|
print("JIT Enabled: \(isJITEnabled())")
|
||||||
|
|
||||||
initializeSDL()
|
initializeSDL()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +90,20 @@ struct ContentView: View {
|
|||||||
} else {
|
} else {
|
||||||
ZStack {
|
ZStack {
|
||||||
emulationView
|
emulationView
|
||||||
|
.onAppear() {
|
||||||
|
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
|
||||||
|
/*
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||||
|
timer.invalidate()
|
||||||
|
quits = quit
|
||||||
|
|
||||||
|
if quits {
|
||||||
|
quit = false
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -94,10 +115,10 @@ struct ContentView: View {
|
|||||||
isAnimating = false
|
isAnimating = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
EmulationView()
|
VStack {
|
||||||
.onAppear() {
|
|
||||||
isAnimating = false
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -131,7 +152,6 @@ struct ContentView: View {
|
|||||||
queue: .main) { notification in
|
queue: .main) { notification in
|
||||||
if let controller = notification.object as? GCController {
|
if let controller = notification.object as? GCController {
|
||||||
print("Controller connected: \(controller.productCategory)")
|
print("Controller connected: \(controller.productCategory)")
|
||||||
nativeControllers[controller] = .init(controller)
|
|
||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,8 +163,6 @@ struct ContentView: View {
|
|||||||
queue: .main) { notification in
|
queue: .main) { notification in
|
||||||
if let controller = notification.object as? GCController {
|
if let controller = notification.object as? GCController {
|
||||||
print("Controller disconnected: \(controller.productCategory)")
|
print("Controller disconnected: \(controller.productCategory)")
|
||||||
nativeControllers[controller]?.cleanup()
|
|
||||||
nativeControllers[controller] = nil
|
|
||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,12 +193,14 @@ struct ContentView: View {
|
|||||||
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
|
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
|
||||||
|
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
|
// Background track
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
|
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
|
||||||
.foregroundColor(.gray.opacity(0.3))
|
.foregroundColor(.gray.opacity(0.3))
|
||||||
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
|
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
|
||||||
|
|
||||||
|
// Animated loading bar
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
|
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
|
||||||
@ -248,9 +268,15 @@ struct ContentView: View {
|
|||||||
))
|
))
|
||||||
|
|
||||||
let isJIT = isJITEnabled()
|
let isJIT = isJITEnabled()
|
||||||
if !isJIT {
|
|
||||||
useTrollStore ? askForJIT() : enableJITEB()
|
if !isJIT, useTrollStore {
|
||||||
|
askForJIT()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isJIT, jitStreamerEB {
|
||||||
|
enableJITEB()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +285,7 @@ struct ContentView: View {
|
|||||||
private func initializeSDL() {
|
private func initializeSDL() {
|
||||||
setMoltenVKSettings()
|
setMoltenVKSettings()
|
||||||
SDL_SetMainReady() // Sets SDL Ready
|
SDL_SetMainReady() // Sets SDL Ready
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true
|
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true (Check out SDL2 Documentation here)
|
||||||
SDL_Init(SdlInitFlags) // Initialises SDL2
|
SDL_Init(SdlInitFlags) // Initialises SDL2
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
@ -280,9 +306,8 @@ struct ContentView: View {
|
|||||||
self.onscreencontroller = onscreen
|
self.onscreencontroller = onscreen
|
||||||
}
|
}
|
||||||
|
|
||||||
controllersList.removeAll(where: { $0.id == "0" || (!$0.name.starts(with: "GC - ") && $0 != onscreencontroller) })
|
controllersList.removeAll(where: { $0.id == "0"})
|
||||||
controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") }
|
|
||||||
|
|
||||||
currentControllers = []
|
currentControllers = []
|
||||||
|
|
||||||
if controllersList.count == 1 {
|
if controllersList.count == 1 {
|
||||||
@ -297,6 +322,27 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
private func start(displayid: UInt32) {
|
||||||
@ -351,10 +397,3 @@ func loadSettings() -> Ryujinx.Configuration? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array {
|
|
||||||
@inlinable public mutating func mutableForEach(_ body: (inout Element) throws -> Void) rethrows {
|
|
||||||
for index in self.indices {
|
|
||||||
try body(&self[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -129,8 +129,6 @@ struct ControllerView: View {
|
|||||||
struct ShoulderButtonsViewLeft: View {
|
struct ShoulderButtonsViewLeft: View {
|
||||||
@State var width: CGFloat = 160
|
@State var width: CGFloat = 160
|
||||||
@State var height: CGFloat = 20
|
@State var height: CGFloat = 20
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
ButtonView(button: .leftTrigger)
|
ButtonView(button: .leftTrigger)
|
||||||
@ -144,9 +142,6 @@ struct ShoulderButtonsViewLeft: View {
|
|||||||
width *= 1.2
|
width *= 1.2
|
||||||
height *= 1.2
|
height *= 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
width *= CGFloat(controllerScale)
|
|
||||||
height *= CGFloat(controllerScale)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,8 +149,6 @@ struct ShoulderButtonsViewLeft: View {
|
|||||||
struct ShoulderButtonsViewRight: View {
|
struct ShoulderButtonsViewRight: View {
|
||||||
@State var width: CGFloat = 160
|
@State var width: CGFloat = 160
|
||||||
@State var height: CGFloat = 20
|
@State var height: CGFloat = 20
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
ButtonView(button: .rightShoulder)
|
ButtonView(button: .rightShoulder)
|
||||||
@ -169,16 +162,12 @@ struct ShoulderButtonsViewRight: View {
|
|||||||
width *= 1.2
|
width *= 1.2
|
||||||
height *= 1.2
|
height *= 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
width *= CGFloat(controllerScale)
|
|
||||||
height *= CGFloat(controllerScale)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DPadView: View {
|
struct DPadView: View {
|
||||||
@State var size: CGFloat = 145
|
@State var size: CGFloat = 145
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ButtonView(button: .dPadUp)
|
ButtonView(button: .dPadUp)
|
||||||
@ -195,16 +184,12 @@ struct DPadView: View {
|
|||||||
if UIDevice.current.systemName.contains("iPadOS") {
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
size *= 1.2
|
size *= 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
size *= CGFloat(controllerScale)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ABXYView: View {
|
struct ABXYView: View {
|
||||||
@State var size: CGFloat = 145
|
@State var size: CGFloat = 145
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ButtonView(button: .X)
|
ButtonView(button: .X)
|
||||||
@ -221,8 +206,6 @@ struct ABXYView: View {
|
|||||||
if UIDevice.current.systemName.contains("iPadOS") {
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
size *= 1.2
|
size *= 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
size *= CGFloat(controllerScale)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +218,6 @@ struct ButtonView: View {
|
|||||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -274,9 +256,6 @@ struct ButtonView: View {
|
|||||||
width *= 1.2
|
width *= 1.2
|
||||||
height *= 1.2
|
height *= 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
width *= CGFloat(controllerScale)
|
|
||||||
height *= CGFloat(controllerScale)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,14 +13,11 @@ public struct Joystick: View {
|
|||||||
@State var iscool: Bool? = nil
|
@State var iscool: Bool? = nil
|
||||||
|
|
||||||
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
var dragDiameter: CGFloat {
|
var dragDiameter: CGFloat {
|
||||||
var selfs = CGFloat(160)
|
var selfs = CGFloat(160)
|
||||||
selfs *= controllerScale
|
|
||||||
if UIDevice.current.systemName.contains("iPadOS") {
|
if UIDevice.current.systemName.contains("iPadOS") {
|
||||||
return selfs * 1.2
|
return selfs * 1.2
|
||||||
}
|
}
|
||||||
|
|
||||||
return selfs
|
return selfs
|
||||||
}
|
}
|
||||||
private let shape: JoystickShape = .circle
|
private let shape: JoystickShape = .circle
|
||||||
|
@ -11,18 +11,16 @@ import SwiftUI
|
|||||||
struct EmulationView: View {
|
struct EmulationView: View {
|
||||||
@AppStorage("isVirtualController") var isVCA: Bool = true
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
||||||
@State var isPresentedThree: Bool = false
|
|
||||||
@State var isAirplaying = Air.shared.connected
|
@State var isAirplaying = Air.shared.connected
|
||||||
@Environment(\.scenePhase) var scenePhase
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if isAirplaying {
|
if isAirplaying {
|
||||||
Text("")
|
Text("")
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
Air.play(AnyView(MetalView(airplay: true).ignoresSafeArea()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MetalView() // The Emulation View
|
MetalView(airplay: false) // The Emulation View
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
}
|
}
|
||||||
@ -33,7 +31,6 @@ struct EmulationView: View {
|
|||||||
ControllerView() // Virtual Controller
|
ControllerView() // Virtual Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ssb {
|
if ssb {
|
||||||
Group {
|
Group {
|
||||||
VStack {
|
VStack {
|
||||||
|
@ -10,7 +10,7 @@ import MetalKit
|
|||||||
|
|
||||||
struct MetalView: UIViewRepresentable {
|
struct MetalView: UIViewRepresentable {
|
||||||
|
|
||||||
var airplay: Bool = Air.shared.connected // just in case :3
|
var airplay: Bool // just in case :3
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
let metalLayer = Ryujinx.shared.metalLayer!
|
let metalLayer = Ryujinx.shared.metalLayer!
|
||||||
|
@ -14,52 +14,45 @@ struct GameInfoSheet: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
List {
|
VStack {
|
||||||
Section {}
|
if let icon = game.icon {
|
||||||
header: {
|
Image(uiImage: icon)
|
||||||
VStack(alignment: .center) {
|
.resizable()
|
||||||
if let icon = game.icon {
|
.aspectRatio(contentMode: .fit)
|
||||||
Image(uiImage: icon)
|
.frame(width: 250, height: 250)
|
||||||
.resizable()
|
.cornerRadius(10)
|
||||||
.aspectRatio(contentMode: .fit)
|
.padding()
|
||||||
.frame(width: 250, height: 250)
|
.contextMenu {
|
||||||
.cornerRadius(10)
|
Button {
|
||||||
.padding()
|
UIImageWriteToSavedPhotosAlbum(icon, nil, nil, nil)
|
||||||
.contextMenu {
|
} label: {
|
||||||
Button {
|
Label("Save to Photos", systemImage: "square.and.arrow.down")
|
||||||
UIImageWriteToSavedPhotosAlbum(icon, nil, nil, nil)
|
}
|
||||||
} label: {
|
|
||||||
Label("Save to Photos", systemImage: "square.and.arrow.down")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Image(systemName: "questionmark.circle")
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
.frame(width: 150, height: 150)
|
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
VStack(alignment: .center) {
|
} else {
|
||||||
Text("**\(game.titleName)** | \(game.titleId.capitalized)")
|
Image(systemName: "questionmark.circle")
|
||||||
.multilineTextAlignment(.center)
|
.resizable()
|
||||||
Text(game.developer)
|
.aspectRatio(contentMode: .fit)
|
||||||
.font(.caption)
|
.frame(width: 150, height: 150)
|
||||||
.foregroundStyle(.secondary)
|
.padding()
|
||||||
}
|
|
||||||
.padding(.vertical, 3)
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
VStack(alignment: .leading) {
|
||||||
Text("**Version**")
|
Text("**\(game.titleName)** | \(game.titleId.capitalized)")
|
||||||
Spacer()
|
Text(game.developer)
|
||||||
Text(game.version)
|
.font(.caption)
|
||||||
.foregroundStyle(Color.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
HStack {
|
.padding(.vertical, 3)
|
||||||
Text("**Title ID**")
|
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("Information")
|
||||||
|
.font(.title2)
|
||||||
|
.bold()
|
||||||
|
|
||||||
|
Text("**Version:** \(game.version)")
|
||||||
|
Text("**Title ID:** \(game.titleId)")
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
UIPasteboard.general.string = game.titleId
|
UIPasteboard.general.string = game.titleId
|
||||||
@ -67,32 +60,15 @@ struct GameInfoSheet: View {
|
|||||||
Text("Copy Title ID")
|
Text("Copy Title ID")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
Text("**Game Size:** \(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
||||||
Text(game.titleId)
|
Text("**File Type:** .\(getFileType(game.fileURL))")
|
||||||
.foregroundStyle(Color.secondary)
|
Text("**Game URL:** \(trimGameURL(game.fileURL))")
|
||||||
}
|
}
|
||||||
HStack {
|
|
||||||
Text("**Game Size**")
|
|
||||||
Spacer()
|
|
||||||
Text("\(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
|
||||||
.foregroundStyle(Color.secondary)
|
|
||||||
}
|
|
||||||
HStack {
|
|
||||||
Text("**File Type**")
|
|
||||||
Spacer()
|
|
||||||
Text(getFileType(game.fileURL))
|
|
||||||
.foregroundStyle(Color.secondary)
|
|
||||||
}
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text("**Game URL**")
|
|
||||||
Text(trimGameURL(game.fileURL))
|
|
||||||
.foregroundStyle(Color.secondary)
|
|
||||||
}
|
|
||||||
} header: {
|
|
||||||
Text("Information")
|
|
||||||
}
|
}
|
||||||
.headerProminence(.increased)
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 5)
|
||||||
.navigationTitle(game.titleName)
|
.navigationTitle(game.titleName)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@ -127,6 +103,10 @@ struct GameInfoSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getFileType(_ url: URL) -> String {
|
func getFileType(_ url: URL) -> String {
|
||||||
url.pathExtension
|
let path = url.path
|
||||||
|
if let range = path.range(of: ".") {
|
||||||
|
return String(path[range.upperBound...])
|
||||||
|
}
|
||||||
|
return "Unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ struct GameLibraryView: View {
|
|||||||
@State var startgame = false
|
@State var startgame = false
|
||||||
@State var isSelectingGameFile = false
|
@State var isSelectingGameFile = false
|
||||||
@State var isViewingGameInfo: Bool = false
|
@State var isViewingGameInfo: Bool = false
|
||||||
@State var isSelectingGameUpdate: Bool = false
|
|
||||||
@State var isSelectingGameDLC: Bool = false
|
|
||||||
@State var gameInfo: Game?
|
@State var gameInfo: Game?
|
||||||
var games: Binding<[Game]> {
|
var games: Binding<[Game]> {
|
||||||
Binding(
|
Binding(
|
||||||
@ -39,107 +37,121 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
var filteredGames: [Game] {
|
var filteredGames: [Game] {
|
||||||
if searchText.isEmpty {
|
if searchText.isEmpty {
|
||||||
return Ryujinx.shared.games.filter { game in
|
return Ryujinx.shared.games
|
||||||
!realRecentGames.contains(where: { $0.fileURL == game.fileURL })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Ryujinx.shared.games.filter {
|
return Ryujinx.shared.games.filter {
|
||||||
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
|
||||||
$0.developer.localizedCaseInsensitiveContains(searchText)
|
$0.developer.localizedCaseInsensitiveContains(searchText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var realRecentGames: [Game] {
|
|
||||||
let games = Ryujinx.shared.games
|
|
||||||
return recentGames.compactMap { recentGame in
|
|
||||||
games.first(where: { $0.fileURL == recentGame.fileURL })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
List {
|
ScrollView {
|
||||||
if Ryujinx.shared.games.isEmpty {
|
LazyVStack(alignment: .leading, spacing: 20) {
|
||||||
VStack(spacing: 16) {
|
if !isSearching {
|
||||||
Image(systemName: "gamecontroller.fill")
|
Text("Games")
|
||||||
.font(.system(size: 64))
|
.font(.system(size: 34, weight: .bold))
|
||||||
.foregroundColor(.secondary.opacity(0.7))
|
.padding(.horizontal)
|
||||||
.padding(.top, 60)
|
.padding(.top, 12)
|
||||||
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)
|
if Ryujinx.shared.games.isEmpty {
|
||||||
} else {
|
VStack(spacing: 16) {
|
||||||
if !isSearching && !realRecentGames.isEmpty {
|
Image(systemName: "gamecontroller.fill")
|
||||||
Section {
|
.font(.system(size: 64))
|
||||||
ForEach(realRecentGames) { game in
|
.foregroundColor(.secondary.opacity(0.7))
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
.padding(.top, 60)
|
||||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
Text("No Games Found")
|
||||||
Button(role: .destructive) {
|
.font(.title2.bold())
|
||||||
removeFromRecentGames(game)
|
.foregroundColor(.primary)
|
||||||
} label: {
|
Text("Add ROM, Keys and Firmware to get started")
|
||||||
Label("Delete", systemImage: "trash")
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text("Recent")
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
}
|
Text("All Games")
|
||||||
|
.font(.title2.bold())
|
||||||
Section {
|
.padding(.horizontal)
|
||||||
ForEach(filteredGames) { game in
|
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
LazyVStack(spacing: 2) {
|
||||||
|
ForEach(filteredGames) { game in
|
||||||
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
|
.onTapGesture {
|
||||||
|
addToRecentGames(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyVStack(spacing: 2) {
|
||||||
|
ForEach(filteredGames) { game in
|
||||||
|
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||||
|
.onTapGesture {
|
||||||
|
addToRecentGames(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text("Others")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ForEach(filteredGames) { game in
|
|
||||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.onAppear {
|
||||||
.navigationTitle("Games")
|
loadRecentGames()
|
||||||
.navigationBarTitleDisplayMode(.large)
|
|
||||||
.onAppear {
|
|
||||||
loadRecentGames()
|
|
||||||
|
|
||||||
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
let firmware = Ryujinx.shared.fetchFirmwareVersion()
|
||||||
firmwareversion = (firmware == "" ? "0" : firmware)
|
firmwareversion = (firmware == "" ? "0" : firmware)
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
do {
|
do {
|
||||||
let fun = url.startAccessingSecurityScopedResource()
|
let fun = url.startAccessingSecurityScopedResource()
|
||||||
let path = url.path
|
let path = url.path
|
||||||
|
|
||||||
Ryujinx.shared.installFirmware(firmwarePath: path)
|
Ryujinx.shared.installFirmware(firmwarePath: path)
|
||||||
|
|
||||||
firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion())
|
firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion())
|
||||||
if fun {
|
if fun {
|
||||||
url.stopAccessingSecurityScopedResource()
|
url.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
print(error)
|
||||||
}
|
}
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Button {
|
Button {
|
||||||
isSelectingGameFile.toggle()
|
isSelectingGameFile.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus")
|
Image(systemName: "plus")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Menu {
|
Menu {
|
||||||
Text("Firmware Version: \(firmwareversion)")
|
Text("Firmware Version: \(firmwareversion)")
|
||||||
@ -201,17 +213,13 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: startemu) { game in
|
|
||||||
guard let game else { return }
|
|
||||||
addToRecentGames(game)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
.searchable(text: $searchText)
|
.searchable(text: $searchText)
|
||||||
.animation(.easeInOut, value: searchText)
|
|
||||||
.onChange(of: searchText) { _ in
|
.onChange(of: searchText) { _ in
|
||||||
isSearching = !searchText.isEmpty
|
isSearching = !searchText.isEmpty
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .folder, .nsp, .xci]) { result in
|
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .folder]) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
guard url.startAccessingSecurityScopedResource() else {
|
||||||
@ -269,12 +277,6 @@ struct GameLibraryView: View {
|
|||||||
print("File import failed: \(err.localizedDescription)")
|
print("File import failed: \(err.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isSelectingGameUpdate) {
|
|
||||||
UpdateManagerSheet(game: $gameInfo)
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $isSelectingGameDLC) {
|
|
||||||
DLCManagerSheet(game: $gameInfo)
|
|
||||||
}
|
|
||||||
.sheet(isPresented: Binding(
|
.sheet(isPresented: Binding(
|
||||||
get: { isViewingGameInfo && gameInfo != nil },
|
get: { isViewingGameInfo && gameInfo != nil },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
@ -290,9 +292,10 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func addToRecentGames(_ game: Game) {
|
private func addToRecentGames(_ game: Game) {
|
||||||
recentGames.removeAll { $0.titleId == game.titleId }
|
recentGames.removeAll { $0.id == game.id }
|
||||||
|
|
||||||
recentGames.insert(game, at: 0)
|
recentGames.insert(game, at: 0)
|
||||||
|
|
||||||
if recentGames.count > 5 {
|
if recentGames.count > 5 {
|
||||||
@ -301,12 +304,7 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
saveRecentGames()
|
saveRecentGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func removeFromRecentGames(_ game: Game) {
|
|
||||||
recentGames.removeAll { $0.titleId == game.titleId }
|
|
||||||
saveRecentGames()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func saveRecentGames() {
|
private func saveRecentGames() {
|
||||||
do {
|
do {
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
@ -327,7 +325,8 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Delete Game Function
|
|
||||||
|
// MARK: - Delete Game Function
|
||||||
func deleteGame(game: Game) {
|
func deleteGame(game: Game) {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
do {
|
do {
|
||||||
@ -340,7 +339,7 @@ struct GameLibraryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Game Model
|
// MARK: -Game Model
|
||||||
extension Game: Codable {
|
extension Game: Codable {
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case titleName, titleId, developer, version, fileURL
|
case titleName, titleId, developer, version, fileURL
|
||||||
@ -369,21 +368,64 @@ extension Game: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Game List Item
|
// MARK: -Recent Game Card
|
||||||
|
struct RecentGameCard: View {
|
||||||
|
let game: Game
|
||||||
|
@Binding var startemu: Game?
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: {
|
||||||
|
startemu = game
|
||||||
|
}) {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
if let icon = game.icon {
|
||||||
|
Image(uiImage: icon)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: 140, height: 140)
|
||||||
|
.cornerRadius(12)
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.fill(colorScheme == .dark ?
|
||||||
|
Color(.systemGray5) : Color(.systemGray6))
|
||||||
|
.frame(width: 140, height: 140)
|
||||||
|
|
||||||
|
Image(systemName: "gamecontroller.fill")
|
||||||
|
.font(.system(size: 40))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(game.titleName)
|
||||||
|
.font(.subheadline.bold())
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
Text(game.developer)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -Game List Item
|
||||||
struct GameListRow: View {
|
struct GameListRow: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
@Binding var startemu: Game?
|
@Binding var startemu: Game?
|
||||||
@Binding var games: [Game] // Add this binding
|
@Binding var games: [Game] // Add this binding
|
||||||
@Binding var isViewingGameInfo: Bool
|
@Binding var isViewingGameInfo: Bool
|
||||||
@Binding var isSelectingGameUpdate: Bool
|
|
||||||
@Binding var isSelectingGameDLC: Bool
|
|
||||||
@Binding var gameInfo: Game?
|
@Binding var gameInfo: Game?
|
||||||
@State var gametoDelete: Game?
|
@State var gametoDelete: Game?
|
||||||
@State var showGameDeleteConfirmation: Bool = false
|
@State var showGameDeleteConfirmation: Bool = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
@AppStorage("portal") var gamepo = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
startemu = game
|
startemu = game
|
||||||
@ -400,7 +442,7 @@ struct GameListRow: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.fill(colorScheme == .dark ?
|
.fill(colorScheme == .dark ?
|
||||||
Color(.systemGray5) : Color(.systemGray6))
|
Color(.systemGray5) : Color(.systemGray6))
|
||||||
.frame(width: 45, height: 45)
|
.frame(width: 45, height: 45)
|
||||||
|
|
||||||
Image(systemName: "gamecontroller.fill")
|
Image(systemName: "gamecontroller.fill")
|
||||||
@ -427,54 +469,36 @@ struct GameListRow: View {
|
|||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
}
|
}
|
||||||
}
|
.padding(.horizontal)
|
||||||
.contextMenu {
|
.padding(.vertical, 8)
|
||||||
Section {
|
.background(Color(.systemBackground))
|
||||||
Button {
|
.contextMenu {
|
||||||
startemu = game
|
Section {
|
||||||
} label: {
|
Button {
|
||||||
Label("Play Now", systemImage: "play.fill")
|
startemu = game
|
||||||
}
|
} label: {
|
||||||
|
Label("Play Now", systemImage: "play.fill")
|
||||||
Button {
|
}
|
||||||
gameInfo = game
|
|
||||||
isViewingGameInfo.toggle()
|
Button {
|
||||||
|
gameInfo = game
|
||||||
if game.titleName.lowercased() == "portal" {
|
isViewingGameInfo.toggle()
|
||||||
gamepo = true
|
} label: {
|
||||||
} else if game.titleName.lowercased() == "portal 2" {
|
Label("Game Info", systemImage: "info.circle")
|
||||||
gamepo = true
|
|
||||||
}
|
}
|
||||||
} label: {
|
|
||||||
Label("Game Info", systemImage: "info.circle")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Section {
|
||||||
Section {
|
Button(role: .destructive) {
|
||||||
Button {
|
gametoDelete = game
|
||||||
gameInfo = game
|
showGameDeleteConfirmation.toggle()
|
||||||
isSelectingGameUpdate.toggle()
|
} label: {
|
||||||
} label: {
|
Label("Delete", systemImage: "trash")
|
||||||
Label("Game Update Manager", systemImage: "chevron.up.circle")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
gameInfo = game
|
|
||||||
isSelectingGameDLC.toggle()
|
|
||||||
} label: {
|
|
||||||
Label("Game DLC Manager", systemImage: "plus.viewfinder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section {
|
|
||||||
Button(role: .destructive) {
|
|
||||||
gametoDelete = game
|
|
||||||
showGameDeleteConfirmation.toggle()
|
|
||||||
} label: {
|
|
||||||
Label("Delete", systemImage: "trash")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
.confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) {
|
.confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) {
|
||||||
Button("Delete", role: .destructive) {
|
Button("Delete", role: .destructive) {
|
||||||
if let game = gametoDelete {
|
if let game = gametoDelete {
|
||||||
@ -497,3 +521,4 @@ struct GameListRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
//import SwiftSVG
|
import SwiftSVG
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@Binding var config: Ryujinx.Configuration
|
@Binding var config: Ryujinx.Configuration
|
||||||
@ -40,13 +40,10 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
@AppStorage("oldWindowCode") var windowCode: Bool = false
|
@AppStorage("oldWindowCode") var windowCode: Bool = false
|
||||||
|
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
|
|
||||||
@State private var showResolutionInfo = false
|
@State private var showResolutionInfo = false
|
||||||
@State private var showAnisotropicInfo = false
|
@State private var showAnisotropicInfo = false
|
||||||
@State private var showControllerInfo = false
|
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@AppStorage("portal") var gamepo = false
|
|
||||||
|
|
||||||
var filteredMemoryModes: [(String, String)] {
|
var filteredMemoryModes: [(String, String)] {
|
||||||
guard !searchText.isEmpty else { return memoryManagerModes }
|
guard !searchText.isEmpty else { return memoryManagerModes }
|
||||||
@ -275,56 +272,17 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
// Input Settings
|
// Input Settings
|
||||||
Section {
|
Section {
|
||||||
Toggle(isOn: $config.macroHLE) {
|
|
||||||
labelWithIcon("Player 1 to Handheld Input", iconName: "formfitting.gamecontroller")
|
Toggle(isOn: $config.listinputids) {
|
||||||
}.tint(.blue)
|
labelWithIcon("List Input IDs", iconName: "list.bullet")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
|
||||||
Toggle(isOn: $ryuDemo) {
|
Toggle(isOn: $ryuDemo) {
|
||||||
labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw")
|
labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
|
||||||
HStack {
|
|
||||||
labelWithIcon("On-Screen Controller Scale", iconName: "magnifyingglass")
|
|
||||||
.font(.headline)
|
|
||||||
Spacer()
|
|
||||||
Button {
|
|
||||||
showControllerInfo.toggle()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "info.circle")
|
|
||||||
.symbolRenderingMode(.hierarchical)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.help("Learn more about On-Screen Controller Scale")
|
|
||||||
.alert(isPresented: $showControllerInfo) {
|
|
||||||
Alert(
|
|
||||||
title: Text("On-Screen Controller Scale"),
|
|
||||||
message: Text("Adjust the On-Screen Controller size."),
|
|
||||||
dismissButton: .default(Text("OK"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider(value: $controllerScale, in: 0.1...3.0, step: 0.05) {
|
|
||||||
Text("Resolution Scale")
|
|
||||||
} minimumValueLabel: {
|
|
||||||
Text("0.1x")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
} maximumValueLabel: {
|
|
||||||
Text("3.0x")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
Text("\(controllerScale, specifier: "%.2f")x")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
} header: {
|
} header: {
|
||||||
Text("Input Settings")
|
Text("Input Settings")
|
||||||
.font(.title3.weight(.semibold))
|
.font(.title3.weight(.semibold))
|
||||||
@ -334,35 +292,6 @@ struct SettingsView: View {
|
|||||||
Text("Configure input devices and on-screen controls for easier navigation and play.")
|
Text("Configure input devices and on-screen controls for easier navigation and play.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language and Region Settings
|
|
||||||
Section {
|
|
||||||
Picker(selection: $config.language) {
|
|
||||||
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
|
|
||||||
Text(ratio.displayName).tag(ratio)
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
labelWithIcon("Language", iconName: "character.bubble")
|
|
||||||
}
|
|
||||||
|
|
||||||
Picker(selection: $config.regioncode) {
|
|
||||||
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
|
|
||||||
Text(ratio.displayName).tag(ratio)
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
labelWithIcon("Region", iconName: "globe")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// globe
|
|
||||||
} header: {
|
|
||||||
Text("Language and Region Settings")
|
|
||||||
.font(.title3.weight(.semibold))
|
|
||||||
.textCase(nil)
|
|
||||||
.headerProminence(.increased)
|
|
||||||
} footer: {
|
|
||||||
Text("Configure the System Language and the Region.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// CPU Mode
|
// CPU Mode
|
||||||
Section {
|
Section {
|
||||||
if filteredMemoryModes.isEmpty {
|
if filteredMemoryModes.isEmpty {
|
||||||
@ -385,16 +314,16 @@ struct SettingsView: View {
|
|||||||
if let cpuInfo = getCPUInfo(), cpuInfo.hasPrefix("Apple M") {
|
if let cpuInfo = getCPUInfo(), cpuInfo.hasPrefix("Apple M") {
|
||||||
if #available (iOS 16.4, *) {
|
if #available (iOS 16.4, *) {
|
||||||
Toggle(isOn: .constant(false)) {
|
Toggle(isOn: .constant(false)) {
|
||||||
labelWithIcon("Hypervisor", iconName: "bolt")
|
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
print("CPU Info: \(cpuInfo)")
|
print("CPU Info: \(cpuInfo)")
|
||||||
}
|
}
|
||||||
} else if checkAppEntitlement("com.apple.private.hypervisor") {
|
} else if getEntitlementValue("com.apple.private.hypervisor") {
|
||||||
Toggle(isOn: $config.hypervisor) {
|
Toggle(isOn: $config.hypervisor) {
|
||||||
labelWithIcon("Hypervisor", iconName: "bolt")
|
labelWithIcon("Hypervisor", iconName: "bolt.fill")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
@ -439,42 +368,36 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
|
|
||||||
// if #available(iOS 17.0.1, *) {
|
if #available(iOS 17.0.1, *) {
|
||||||
// Toggle(isOn: $jitStreamerEB) {
|
Toggle(isOn: $jitStreamerEB) {
|
||||||
// labelWithIcon("JitStreamer EB", iconName: "bolt.heart")
|
labelWithIcon("JitStreamer EB", iconName: "bolt.heart")
|
||||||
// }
|
|
||||||
// .tint(.blue)
|
|
||||||
// .contextMenu {
|
|
||||||
// Button {
|
|
||||||
// if let mainWindow = UIApplication.shared.windows.last {
|
|
||||||
// let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
|
||||||
//
|
|
||||||
// let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
|
||||||
// UIApplication.shared.open(URL(string: "https://jkcoxson.com/jitstreamer")!)
|
|
||||||
// }
|
|
||||||
// alertController.addAction(learnMoreButton)
|
|
||||||
//
|
|
||||||
// let doneButton = UIAlertAction(title: "Done", style: .cancel, handler: nil)
|
|
||||||
// alertController.addAction(doneButton)
|
|
||||||
//
|
|
||||||
// mainWindow.rootViewController?.present(alertController, animated: true)
|
|
||||||
// }
|
|
||||||
// } label: {
|
|
||||||
// Text("About")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
Toggle(isOn: $useTrollStore) {
|
|
||||||
HStack(spacing: 8) {
|
|
||||||
Image("troll")
|
|
||||||
.symbolRenderingMode(.hierarchical)
|
|
||||||
.foregroundStyle(.blue)
|
|
||||||
Text("TrollStore JIT")
|
|
||||||
}
|
|
||||||
.font(.body)
|
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
// }
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
if let mainWindow = UIApplication.shared.windows.last {
|
||||||
|
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
||||||
|
|
||||||
|
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
||||||
|
UIApplication.shared.open(URL(string: "https://jkcoxson.com/jitstreamer")!)
|
||||||
|
}
|
||||||
|
alertController.addAction(learnMoreButton)
|
||||||
|
|
||||||
|
let doneButton = UIAlertAction(title: "Done", style: .cancel, handler: nil)
|
||||||
|
alertController.addAction(doneButton)
|
||||||
|
|
||||||
|
mainWindow.rootViewController?.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text("About")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toggle(isOn: $useTrollStore) {
|
||||||
|
labelWithIcon("TrollStore JIT", iconName: "troll.svg")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
}
|
||||||
|
|
||||||
Toggle(isOn: $syncqsubmits) {
|
Toggle(isOn: $syncqsubmits) {
|
||||||
labelWithIcon("MVK: Synchronous Queue Submits", iconName: "line.diagonal")
|
labelWithIcon("MVK: Synchronous Queue Submits", iconName: "line.diagonal")
|
||||||
@ -517,43 +440,16 @@ struct SettingsView: View {
|
|||||||
Text("Enable trace and debug logs for advanced troubleshooting (Note: This degrades performance),\nEnable Screenshot Button for better screenshots\nand Enable TrollStore for automatic TrollStore JIT.")
|
Text("Enable trace and debug logs for advanced troubleshooting (Note: This degrades performance),\nEnable Screenshot Button for better screenshots\nand Enable TrollStore for automatic TrollStore JIT.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info
|
|
||||||
Section {
|
|
||||||
let totalMemory = ProcessInfo.processInfo.physicalMemory
|
|
||||||
let model = getDeviceModel()
|
|
||||||
let deviceType = model.hasPrefix("iPad") ? "iPadOS" :
|
|
||||||
model.hasPrefix("iPhone") ? "iOS" :
|
|
||||||
"macOS"
|
|
||||||
|
|
||||||
let iconName = model.hasPrefix("iPad") ? "ipad.landscape" :
|
|
||||||
model.hasPrefix("iPhone") ? "iphone" :
|
|
||||||
"macwindow"
|
|
||||||
|
|
||||||
labelWithIcon("JIT Acquisition: \(isJITEnabled() ? "Acquired" : "Not Acquired" )", iconName: "bolt.fill")
|
|
||||||
|
|
||||||
labelWithIcon("Increased Memory Limit Entitlement: \(checkAppEntitlement("com.apple.developer.kernel.increased-memory-limit") ? "Enabled" : "Disabled")", iconName: "memorychip")
|
|
||||||
|
|
||||||
labelWithIcon("Device: \(getDeviceModel())", iconName: iconName)
|
|
||||||
|
|
||||||
labelWithIcon("Device Memory: \(String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000))", iconName: "memorychip.fill")
|
|
||||||
|
|
||||||
labelWithIcon("\(deviceType) \(UIDevice.current.systemVersion)", iconName: "applelogo")
|
|
||||||
|
|
||||||
} header: {
|
|
||||||
Text("Information")
|
|
||||||
.font(.title3.weight(.semibold))
|
|
||||||
.textCase(nil)
|
|
||||||
.headerProminence(.increased)
|
|
||||||
} footer: {
|
|
||||||
Text("Shows info about Memory, Entitlement and JIT.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advanced
|
// Advanced
|
||||||
Section {
|
Section {
|
||||||
Toggle(isOn: $windowCode) {
|
labelWithIcon("JIT Acquisition: \(isJITEnabled() ? "Acquired" : "Not Acquired" )", iconName: "bolt.fill")
|
||||||
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
|
|
||||||
|
if #unavailable(iOS 17) {
|
||||||
|
Toggle(isOn: $windowCode) {
|
||||||
|
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
|
||||||
|
|
||||||
DisclosureGroup {
|
DisclosureGroup {
|
||||||
|
|
||||||
@ -606,9 +502,9 @@ struct SettingsView: View {
|
|||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
} footer: {
|
} footer: {
|
||||||
if #available(iOS 17, *) {
|
if #available(iOS 17, *) {
|
||||||
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). \n \n\(gamepo ? "the cake is a lie" : "")")
|
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing).")
|
||||||
} else {
|
} else {
|
||||||
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). If the emulation is not showing (you may hear audio in some games), try enabling \"SDL Window\" \n \n\(gamepo ? "the cake is a lie" : "")")
|
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). If the emulation is not showing (you may hear audio in some games), try enabling \"SDL Window\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,18 +533,6 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeviceModel() -> String {
|
|
||||||
var systemInfo = utsname()
|
|
||||||
uname(&systemInfo)
|
|
||||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
|
||||||
let identifier = machineMirror.children.reduce("") { identifier, element in
|
|
||||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
|
||||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
|
||||||
}
|
|
||||||
return identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func saveSettings() {
|
func saveSettings() {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
|
|
||||||
@ -701,11 +585,56 @@ struct SettingsView: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
|
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: iconName)
|
if iconName.hasSuffix(".svg"){
|
||||||
.symbolRenderingMode(.hierarchical)
|
if let flipimage, flipimage {
|
||||||
.foregroundStyle(.blue)
|
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)
|
Text(text)
|
||||||
}
|
}
|
||||||
.font(.body)
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
//
|
|
||||||
// GameDLCManagerSheet.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by XITRIX on 16/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
|
|
||||||
struct DownloadableContentNca: Codable, Hashable {
|
|
||||||
var fullPath: String
|
|
||||||
var titleId: UInt
|
|
||||||
var enabled: Bool
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case fullPath = "path"
|
|
||||||
case titleId = "title_id"
|
|
||||||
case enabled = "is_enabled"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DownloadableContentContainer: Codable, Hashable {
|
|
||||||
var containerPath: String
|
|
||||||
var downloadableContentNcaList: [DownloadableContentNca]
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
|
||||||
case containerPath = "path"
|
|
||||||
case downloadableContentNcaList = "dlc_nca_list"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DLCManagerSheet: View {
|
|
||||||
@Binding var game: Game!
|
|
||||||
@State private var isSelectingGameDLC = false
|
|
||||||
@State private var dlcs: [DownloadableContentContainer] = []
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
let withIndex = dlcs.enumerated().map { $0 }
|
|
||||||
List(withIndex, id: \.element.containerPath) { index, dlc in
|
|
||||||
Button(action: {
|
|
||||||
let toggle = dlcs[index].downloadableContentNcaList.first?.enabled ?? true
|
|
||||||
dlcs[index].downloadableContentNcaList.mutableForEach { $0.enabled = !toggle }
|
|
||||||
Self.saveDlcs(game, dlc: dlcs)
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text((dlc.containerPath as NSString).lastPathComponent)
|
|
||||||
.foregroundStyle(Color(uiColor: .label))
|
|
||||||
Spacer()
|
|
||||||
if dlc.downloadableContentNcaList.first?.enabled == true {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundStyle(Color.accentColor)
|
|
||||||
.font(.system(size: 24))
|
|
||||||
} else {
|
|
||||||
Image(systemName: "circle")
|
|
||||||
.foregroundStyle(Color(uiColor: .secondaryLabel))
|
|
||||||
.font(.system(size: 24))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.contextMenu {
|
|
||||||
Button {
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent(dlc.containerPath)
|
|
||||||
try? FileManager.default.removeItem(atPath: path.path)
|
|
||||||
dlcs.remove(at: index)
|
|
||||||
Self.saveDlcs(game, dlc: dlcs)
|
|
||||||
} label: {
|
|
||||||
Text("Remove DLC")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle("\(game.titleName) DLCs")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.toolbar {
|
|
||||||
Button("Add", systemImage: "plus") {
|
|
||||||
isSelectingGameDLC = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
dlcs = Self.loadDlc(game)
|
|
||||||
}
|
|
||||||
.fileImporter(isPresented: $isSelectingGameDLC, allowedContentTypes: [.item], allowsMultipleSelection: true) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let urls):
|
|
||||||
for url in urls {
|
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
|
||||||
print("Failed to access security-scoped resource")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer { url.stopAccessingSecurityScopedResource() }
|
|
||||||
|
|
||||||
do {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let dlcDirectory = documentsDirectory.appendingPathComponent("dlc")
|
|
||||||
let romDlcDirectory = dlcDirectory.appendingPathComponent(game.titleId)
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: dlcDirectory.path) {
|
|
||||||
try fileManager.createDirectory(at: dlcDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: romDlcDirectory.path) {
|
|
||||||
try fileManager.createDirectory(at: romDlcDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
let dlcContent = Ryujinx.shared.getDlcNcaList(titleId: game.titleId, path: url.path)
|
|
||||||
guard !dlcContent.isEmpty else { return }
|
|
||||||
|
|
||||||
let destinationURL = romDlcDirectory.appendingPathComponent(url.lastPathComponent)
|
|
||||||
try? fileManager.copyItem(at: url, to: destinationURL)
|
|
||||||
|
|
||||||
let container = DownloadableContentContainer(
|
|
||||||
containerPath: Self.relativeDlcDirectoryPath(for: game, dlcPath: destinationURL),
|
|
||||||
downloadableContentNcaList: dlcContent
|
|
||||||
)
|
|
||||||
dlcs.append(container)
|
|
||||||
|
|
||||||
Self.saveDlcs(game, dlc: dlcs)
|
|
||||||
} catch {
|
|
||||||
print("Error copying game file: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .failure(let err):
|
|
||||||
print("File import failed: \(err.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension DLCManagerSheet {
|
|
||||||
static func loadDlc(_ game: Game) -> [DownloadableContentContainer] {
|
|
||||||
let jsonURL = dlcJsonPath(for: game)
|
|
||||||
guard let data = try? Data(contentsOf: jsonURL),
|
|
||||||
var result = try? JSONDecoder().decode([DownloadableContentContainer].self, from: data)
|
|
||||||
else { return [] }
|
|
||||||
|
|
||||||
result = result.filter { container in
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent(container.containerPath)
|
|
||||||
return FileManager.default.fileExists(atPath: path.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
static func saveDlcs(_ game: Game, dlc: [DownloadableContentContainer]) {
|
|
||||||
guard let data = try? JSONEncoder().encode(dlc) else { return }
|
|
||||||
try? data.write(to: dlcJsonPath(for: game))
|
|
||||||
}
|
|
||||||
|
|
||||||
static func relativeDlcDirectoryPath(for game: Game, dlcPath: URL) -> String {
|
|
||||||
"dlc/\(game.titleId)/\(dlcPath.lastPathComponent)"
|
|
||||||
}
|
|
||||||
|
|
||||||
static func dlcJsonPath(for game: Game) -> URL {
|
|
||||||
URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game.titleId).appendingPathComponent("dlc.json")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension URL {
|
|
||||||
@available(iOS, introduced: 15.0, deprecated: 16.0, message: "Use URL.documentsDirectory on iOS 16 and above")
|
|
||||||
static var documentsDirectory: URL {
|
|
||||||
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
return documentDirectory
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,201 +0,0 @@
|
|||||||
//
|
|
||||||
// GameUpdateManagerSheet.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 16/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UniformTypeIdentifiers
|
|
||||||
|
|
||||||
struct UpdateManagerSheet: View {
|
|
||||||
@State private var items: [String] = []
|
|
||||||
@State private var paths: [URL] = []
|
|
||||||
@State private var selectedItem: String? = nil
|
|
||||||
@Binding var game: Game?
|
|
||||||
@State private var isSelectingGameUpdate = false
|
|
||||||
@State private var jsonURL: URL? = nil
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationView {
|
|
||||||
List(paths, id: \..self, selection: $selectedItem) { item in
|
|
||||||
Button(action: {
|
|
||||||
selectItem(item.lastPathComponent)
|
|
||||||
}) {
|
|
||||||
HStack {
|
|
||||||
Text(item.lastPathComponent)
|
|
||||||
.foregroundStyle(Color(uiColor: .label))
|
|
||||||
Spacer()
|
|
||||||
if selectedItem == "updates/\(game!.titleId)/\(item.lastPathComponent)" {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundStyle(Color.accentColor)
|
|
||||||
.font(.system(size: 24))
|
|
||||||
} else {
|
|
||||||
Image(systemName: "circle")
|
|
||||||
.foregroundStyle(Color(uiColor: .secondaryLabel))
|
|
||||||
.font(.system(size: 24))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.contextMenu {
|
|
||||||
Button {
|
|
||||||
removeUpdate(item)
|
|
||||||
} label: {
|
|
||||||
Text("Remove Update")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
print(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json"))
|
|
||||||
|
|
||||||
loadJSON(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json"))
|
|
||||||
}
|
|
||||||
.navigationTitle("\(game!.titleName) Updates")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.toolbar {
|
|
||||||
Button("Add", systemImage: "plus") {
|
|
||||||
isSelectingGameUpdate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fileImporter(isPresented: $isSelectingGameUpdate, allowedContentTypes: [.item]) { result in
|
|
||||||
switch result {
|
|
||||||
case .success(let url):
|
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
|
||||||
print("Failed to access security-scoped resource")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer { url.stopAccessingSecurityScopedResource() }
|
|
||||||
|
|
||||||
let gameInfo = game!
|
|
||||||
|
|
||||||
do {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let updatedDirectory = documentsDirectory.appendingPathComponent("updates")
|
|
||||||
let romUpdatedDirectory = updatedDirectory.appendingPathComponent(gameInfo.titleId)
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: updatedDirectory.path) {
|
|
||||||
try fileManager.createDirectory(at: updatedDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: romUpdatedDirectory.path) {
|
|
||||||
try fileManager.createDirectory(at: romUpdatedDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
let destinationURL = romUpdatedDirectory.appendingPathComponent(url.lastPathComponent)
|
|
||||||
try? fileManager.copyItem(at: url, to: destinationURL)
|
|
||||||
|
|
||||||
items.append("updates/" + gameInfo.titleId + "/" + url.lastPathComponent)
|
|
||||||
selectItem(url.lastPathComponent)
|
|
||||||
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
|
||||||
loadJSON(jsonURL!)
|
|
||||||
} catch {
|
|
||||||
print("Error copying game file: \(error)")
|
|
||||||
}
|
|
||||||
case .failure(let err):
|
|
||||||
print("File import failed: \(err.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeUpdate(_ game: URL) {
|
|
||||||
let gameString = "updates/\(self.game!.titleId)/\(game.lastPathComponent)"
|
|
||||||
paths.removeAll { $0 == game }
|
|
||||||
items.removeAll { $0 == gameString }
|
|
||||||
|
|
||||||
if selectedItem == gameString {
|
|
||||||
selectedItem = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
try FileManager.default.removeItem(at: game)
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveJSON(selectedItem: selectedItem ?? "")
|
|
||||||
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveJSON(selectedItem: String?) {
|
|
||||||
guard let jsonURL = jsonURL else { return }
|
|
||||||
do {
|
|
||||||
let jsonDict = ["paths": items, "selected": selectedItem ?? self.selectedItem ?? ""] as [String: Any]
|
|
||||||
let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
|
|
||||||
try newData.write(to: jsonURL)
|
|
||||||
} catch {
|
|
||||||
print("Failed to update JSON: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadJSON(_ json: URL) {
|
|
||||||
self.jsonURL = json
|
|
||||||
|
|
||||||
guard let jsonURL else { return }
|
|
||||||
|
|
||||||
do {
|
|
||||||
let data = try Data(contentsOf: jsonURL)
|
|
||||||
if let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
||||||
let list = jsonDict["paths"] as? [String]
|
|
||||||
{
|
|
||||||
|
|
||||||
let filteredList = list.filter { relativePath in
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent(relativePath)
|
|
||||||
return FileManager.default.fileExists(atPath: path.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
let urls: [URL] = filteredList.map { relativePath in
|
|
||||||
URL.documentsDirectory.appendingPathComponent(relativePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
items = filteredList
|
|
||||||
paths = urls
|
|
||||||
selectedItem = jsonDict["selected"] as? String
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Failed to read JSON: \(error)")
|
|
||||||
createDefaultJSON()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDefaultJSON() {
|
|
||||||
guard let jsonURL = jsonURL else { return }
|
|
||||||
let defaultData: [String: Any] = ["selected": "", "paths": []]
|
|
||||||
do {
|
|
||||||
let newData = try JSONSerialization.data(withJSONObject: defaultData, options: .prettyPrinted)
|
|
||||||
try newData.write(to: jsonURL)
|
|
||||||
items = []
|
|
||||||
selectedItem = ""
|
|
||||||
} catch {
|
|
||||||
print("Failed to create default JSON: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectItem(_ item: String) {
|
|
||||||
let newSelection = "updates/\(game!.titleId)/\(item)"
|
|
||||||
|
|
||||||
guard let jsonURL else { return }
|
|
||||||
|
|
||||||
do {
|
|
||||||
let data = try Data(contentsOf: jsonURL)
|
|
||||||
var jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:]
|
|
||||||
|
|
||||||
if let currentSelected = jsonDict["selected"] as? String, currentSelected == newSelection {
|
|
||||||
jsonDict["selected"] = ""
|
|
||||||
selectedItem = ""
|
|
||||||
} else {
|
|
||||||
jsonDict["selected"] = "\(newSelection)"
|
|
||||||
selectedItem = newSelection
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonDict["paths"] = items
|
|
||||||
|
|
||||||
let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
|
|
||||||
try newData.write(to: jsonURL)
|
|
||||||
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
|
||||||
} catch {
|
|
||||||
print("Failed to update JSON: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "Troll-Face.svg",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 26 KiB |
@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// RyujinxBridge.h
|
|
||||||
// RyujinxBridge
|
|
||||||
//
|
|
||||||
// Created by Daniil Vinogradov on 17/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
//! Project version number for RyujinxBridge.
|
|
||||||
FOUNDATION_EXPORT double RyujinxBridgeVersionNumber;
|
|
||||||
|
|
||||||
//! Project version string for RyujinxBridge.
|
|
||||||
FOUNDATION_EXPORT const unsigned char RyujinxBridgeVersionString[];
|
|
||||||
|
|
||||||
// In this header, you should import all the public headers of your framework using statements like #import <RyujinxBridge/PublicHeader.h>
|
|
||||||
#import <RyujinxBridge/bridge.h>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
//
|
|
||||||
// bridge.h
|
|
||||||
// RyujinxBridge
|
|
||||||
//
|
|
||||||
// Created by Daniil Vinogradov on 17/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
void (*messageDelegate)(const char*);
|
|
Binary file not shown.
@ -1,6 +0,0 @@
|
|||||||
framework module RyujinxBridge {
|
|
||||||
umbrella header "RyujinxBridge.h"
|
|
||||||
export *
|
|
||||||
|
|
||||||
module * { export * }
|
|
||||||
}
|
|
Binary file not shown.
@ -1,135 +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>files</key>
|
|
||||||
<dict>
|
|
||||||
<key>Headers/RyujinxBridge.h</key>
|
|
||||||
<data>
|
|
||||||
DKD2r8aJ47TZL7v48UVEfod716g=
|
|
||||||
</data>
|
|
||||||
<key>Headers/bridge.h</key>
|
|
||||||
<data>
|
|
||||||
p75HJMB/G5CAZ+yTApMWmoQP7Lg=
|
|
||||||
</data>
|
|
||||||
<key>Info.plist</key>
|
|
||||||
<data>
|
|
||||||
mWbK0knhX+Q4WAm+hZd8SF0ioS0=
|
|
||||||
</data>
|
|
||||||
<key>Modules/module.modulemap</key>
|
|
||||||
<data>
|
|
||||||
+to1dvHz+3pPZcmBu4qsYsrvt4Y=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>files2</key>
|
|
||||||
<dict>
|
|
||||||
<key>Headers/RyujinxBridge.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
xIPdWru4HW7sRYRg6G+ehIk9V4nX3Uv7kIlE2c/TnjM=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Headers/bridge.h</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
kA+OGSf2EzopJ1KM/+Lp8qHheuFQQYlkkOdMg/ywzH8=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
<key>Modules/module.modulemap</key>
|
|
||||||
<dict>
|
|
||||||
<key>hash2</key>
|
|
||||||
<data>
|
|
||||||
ZTC6KjLczI++298LFW6y9c8aoPQ1LrrsJrDJfrPnZUU=
|
|
||||||
</data>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>rules</key>
|
|
||||||
<dict>
|
|
||||||
<key>^.*</key>
|
|
||||||
<true/>
|
|
||||||
<key>^.*\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>optional</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1000</real>
|
|
||||||
</dict>
|
|
||||||
<key>^.*\.lproj/locversion.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1100</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Base\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1010</real>
|
|
||||||
</dict>
|
|
||||||
<key>^version.plist$</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>rules2</key>
|
|
||||||
<dict>
|
|
||||||
<key>.*\.dSYM($|/)</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>11</real>
|
|
||||||
</dict>
|
|
||||||
<key>^(.*/)?\.DS_Store$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>2000</real>
|
|
||||||
</dict>
|
|
||||||
<key>^.*</key>
|
|
||||||
<true/>
|
|
||||||
<key>^.*\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>optional</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1000</real>
|
|
||||||
</dict>
|
|
||||||
<key>^.*\.lproj/locversion.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1100</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Base\.lproj/</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>1010</real>
|
|
||||||
</dict>
|
|
||||||
<key>^Info\.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^PkgInfo$</key>
|
|
||||||
<dict>
|
|
||||||
<key>omit</key>
|
|
||||||
<true/>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^embedded\.provisionprofile$</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
<key>^version\.plist$</key>
|
|
||||||
<dict>
|
|
||||||
<key>weight</key>
|
|
||||||
<real>20</real>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
Binary file not shown.
Binary file not shown.
@ -15,17 +15,6 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>GCSupportedGameControllers</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>ProfileName</key>
|
|
||||||
<string>ExtendedGamepad</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>ProfileName</key>
|
|
||||||
<string>MicroGamepad</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>melonx</string>
|
<string>melonx</string>
|
||||||
@ -36,11 +25,6 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>LaunchGameIntent</string>
|
<string>LaunchGameIntent</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIBackgroundModes</key>
|
|
||||||
<array>
|
|
||||||
<string>audio</string>
|
|
||||||
<string>processing</string>
|
|
||||||
</array>
|
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
11
src/MeloNX/dotnet.xcconfig.example
Normal file
11
src/MeloNX/dotnet.xcconfig.example
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// dotnet.xcconfig
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by June P on 12/25/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Configuration settings file format documentation can be found at:
|
||||||
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
|
DOTNET_PATH = $(HOME)/.dotnet/dotnet
|
@ -26,16 +26,10 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
|
|||||||
{
|
{
|
||||||
return $"lib{libraryName}.so.{version}";
|
return $"lib{libraryName}.so.{version}";
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS())
|
else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) // TODO: ffmpeg on ios
|
||||||
{
|
{
|
||||||
return $"lib{libraryName}.{version}.dylib";
|
return $"lib{libraryName}.{version}.dylib";
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsIOS())
|
|
||||||
{
|
|
||||||
string libName = $"lib{libraryName}.{version}.dylib";
|
|
||||||
Console.WriteLine($"[iOS] Required firmware library: {libName}");
|
|
||||||
return libName;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}");
|
throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}");
|
||||||
|
@ -9,10 +9,10 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
|||||||
[SupportedOSPlatform("ios")]
|
[SupportedOSPlatform("ios")]
|
||||||
public static partial class MVKInitialization
|
public static partial class MVKInitialization
|
||||||
{
|
{
|
||||||
[LibraryImport("libMoltenVK.dylib")]
|
[LibraryImport("MoltenVK.framework/MoltenVK")]
|
||||||
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
|
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
|
||||||
|
|
||||||
[LibraryImport("libMoltenVK.dylib")]
|
[LibraryImport("MoltenVK.framework/MoltenVK")]
|
||||||
private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
|
private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
|
||||||
|
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
|
@ -601,7 +601,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
if (supportsExtDynamicState)
|
if (supportsExtDynamicState)
|
||||||
{
|
{
|
||||||
// dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
|
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
|
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Headless.SDL2
|
|
||||||
{
|
|
||||||
struct BridgeAlertMessage(string title, string message, string metadata = null)
|
|
||||||
{
|
|
||||||
[JsonProperty("title")]
|
|
||||||
string Title { get; set; } = title;
|
|
||||||
|
|
||||||
[JsonProperty("message")]
|
|
||||||
string Message { get; set; } = message;
|
|
||||||
|
|
||||||
[JsonProperty("metadata")]
|
|
||||||
string Metadata { get; set; } = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MessageBridge
|
|
||||||
{
|
|
||||||
[DllImport("RyujinxBridge.framework/RyujinxBridge", CallingConvention = CallingConvention.Cdecl, EntryPoint="sendMessage")]
|
|
||||||
private static extern void SendMessage(string json);
|
|
||||||
|
|
||||||
public static void SendPayload<T>(T model)
|
|
||||||
{
|
|
||||||
string jsonString = JsonConvert.SerializeObject(new BridgePayload<T>(typeof(T).Name, model));
|
|
||||||
SendMessage(jsonString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct BridgePayload<T>(string type, T model)
|
|
||||||
{
|
|
||||||
[JsonProperty("type")]
|
|
||||||
private string Type { get; set; } = type;
|
|
||||||
|
|
||||||
[JsonProperty("model")]
|
|
||||||
private T Model { get; set; } = model;
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,6 +37,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||||
@ -84,7 +85,6 @@ using ARMeilleure.Translation;
|
|||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Microsoft.Win32.SafeHandles;
|
using Microsoft.Win32.SafeHandles;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
@ -94,7 +94,6 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using SDL2;
|
using SDL2;
|
||||||
using JsonException = System.Text.Json.JsonException;
|
|
||||||
|
|
||||||
namespace Ryujinx.Headless.SDL2
|
namespace Ryujinx.Headless.SDL2
|
||||||
{
|
{
|
||||||
@ -116,15 +115,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
private static bool _enableMouse;
|
private static bool _enableMouse;
|
||||||
|
|
||||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
// [UnmanagedCallersOnly(EntryPoint = "get_dlc_nca_list")]
|
|
||||||
// public static unsafe void CDeclCombine(delegate* unmanaged[Cdecl]<void> combinator) =>
|
|
||||||
// combinator(left, right);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
public delegate void TestDelegate(int a, int b);
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "main_ryujinx_sdl")]
|
[UnmanagedCallersOnly(EntryPoint = "main_ryujinx_sdl")]
|
||||||
public static unsafe int MainExternal(int argCount, IntPtr* pArgs)
|
public static unsafe int MainExternal(int argCount, IntPtr* pArgs)
|
||||||
{
|
{
|
||||||
@ -150,95 +141,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "get_dlc_nca_list")]
|
|
||||||
public static unsafe DlcNcaList GetDlcNcaList(IntPtr titleIdPtr, IntPtr pathPtr)
|
|
||||||
{
|
|
||||||
var titleId = Marshal.PtrToStringAnsi(titleIdPtr);
|
|
||||||
var containerPath = Marshal.PtrToStringAnsi(pathPtr);
|
|
||||||
|
|
||||||
if (!File.Exists(containerPath))
|
|
||||||
{
|
|
||||||
return new DlcNcaList { success = false };
|
|
||||||
}
|
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(containerPath);
|
|
||||||
|
|
||||||
PartitionFileSystem pfs = new();
|
|
||||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
|
||||||
bool containsDlc = false;
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(pfs);
|
|
||||||
|
|
||||||
// TreeIter? parentIter = null;
|
|
||||||
|
|
||||||
List<DlcNcaListItem> listItems = new();
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
|
|
||||||
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
|
||||||
{
|
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != titleId)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"ContainerPath: {containerPath}");
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"TitleId: {nca.Header.TitleId}");
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"fileEntry.FullPath: {fileEntry.FullPath}");
|
|
||||||
|
|
||||||
// parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
|
|
||||||
// ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
|
||||||
|
|
||||||
DlcNcaListItem item = new();
|
|
||||||
CopyStringToFixedArray(fileEntry.FullPath, item.Path, 256);
|
|
||||||
item.TitleId = nca.Header.TitleId;
|
|
||||||
listItems.Add(item);
|
|
||||||
|
|
||||||
containsDlc = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsDlc)
|
|
||||||
{
|
|
||||||
return new DlcNcaList { success = false };
|
|
||||||
// GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new DlcNcaList { success = true, size = (uint) listItems.Count };
|
|
||||||
|
|
||||||
DlcNcaListItem[] items = listItems.ToArray();
|
|
||||||
|
|
||||||
fixed (DlcNcaListItem* p = &items[0])
|
|
||||||
{
|
|
||||||
list.items = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
|
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
|
||||||
public static unsafe int GetFPS()
|
public static unsafe int GetFPS()
|
||||||
@ -327,7 +229,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
var result = Parser.Default.ParseArguments<Options>(args)
|
var result = Parser.Default.ParseArguments<Options>(args)
|
||||||
.WithParsed(options =>
|
.WithParsed(options =>
|
||||||
{
|
{
|
||||||
Load(options);
|
Load(options); // Load is called with the parsed options
|
||||||
})
|
})
|
||||||
.WithNotParsed(errors => errors.Output());
|
.WithNotParsed(errors => errors.Output());
|
||||||
|
|
||||||
@ -402,6 +304,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
if (_window != null)
|
if (_window != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
_window.Exit();
|
_window.Exit();
|
||||||
_emulationContext.Dispose();
|
_emulationContext.Dispose();
|
||||||
_emulationContext = null;
|
_emulationContext = null;
|
||||||
@ -411,19 +314,13 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
|
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
|
||||||
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
|
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
|
||||||
{
|
{
|
||||||
MessageBridge.SendPayload(new BridgeAlertMessage("Text", "Alalallala"));
|
|
||||||
|
|
||||||
if (_virtualFileSystem == null) {
|
if (_virtualFileSystem == null) {
|
||||||
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Marshal.PtrToStringAnsi(extensionPtr);
|
var extension = Marshal.PtrToStringAnsi(extensionPtr);
|
||||||
var stream = OpenFile(descriptor);
|
var stream = OpenFile(descriptor);
|
||||||
|
|
||||||
var gameInfo = GetGameInfo(stream, extension);
|
var gameInfo = GetGameInfo(stream, extension);
|
||||||
if (gameInfo == null) {
|
|
||||||
return new GameInfoNative(0, "", "", "", "", new byte[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GameInfoNative(
|
return new GameInfoNative(
|
||||||
(ulong)gameInfo.FileSize,
|
(ulong)gameInfo.FileSize,
|
||||||
@ -822,8 +719,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
{
|
{
|
||||||
string updatePathRelative = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
// updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected;
|
||||||
updatePath = Path.Combine(AppDataManager.BaseDirPath, updatePathRelative);
|
|
||||||
|
|
||||||
if (File.Exists(updatePath))
|
if (File.Exists(updatePath))
|
||||||
{
|
{
|
||||||
@ -1589,19 +1485,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
public byte[]? Icon;
|
public byte[]? Icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe struct DlcNcaListItem
|
|
||||||
{
|
|
||||||
public fixed byte Path[256];
|
|
||||||
public ulong TitleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe struct DlcNcaList
|
|
||||||
{
|
|
||||||
public bool success;
|
|
||||||
public uint size;
|
|
||||||
public unsafe DlcNcaListItem* items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe struct GameInfoNative
|
public unsafe struct GameInfoNative
|
||||||
{
|
{
|
||||||
public ulong FileSize;
|
public ulong FileSize;
|
||||||
@ -1649,13 +1532,14 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
ImageData = null;
|
ImageData = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void CopyStringToFixedArray(string source, byte* destination, int length)
|
||||||
|
{
|
||||||
|
var span = new Span<byte>(destination, length);
|
||||||
|
span.Clear();
|
||||||
|
Encoding.UTF8.GetBytes(source, span);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe void CopyStringToFixedArray(string source, byte* destination, int length)
|
|
||||||
{
|
|
||||||
var span = new Span<byte>(destination, length);
|
|
||||||
span.Clear();
|
|
||||||
Encoding.UTF8.GetBytes(source, span);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,8 +477,8 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
public bool DisplayMessageDialog(string title, string message)
|
public bool DisplayMessageDialog(string title, string message)
|
||||||
{
|
{
|
||||||
// SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, title, message, WindowHandle);
|
SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, title, message, WindowHandle);
|
||||||
MessageBridge.SendPayload(new BridgeAlertMessage(title, message, "controllerApplet"));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user