Archived
1
0
forked from MeloNX/MeloNX

Compare commits

..

No commits in common. "Message-Bridge" and "XC-ios-ht" have entirely different histories.

57 changed files with 769 additions and 2159 deletions

64
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -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,15 +87,22 @@ 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)
{
_deferredRxProtect.Enqueue((funcOffset, code.Length));
}
else
{
ReprotectAsExecutable(funcOffset, code.Length);
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length); JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
} }
}
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64) else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
unsafe unsafe
@ -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);
{
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64()); int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex); _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,36 +187,21 @@ 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
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
if (allocOffset < 0)
{ {
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); throw new OutOfMemoryException("JIT Cache exhausted.");
_activeRegionIndex = i; }
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
return allocOffset; return allocOffset;
} }
}
int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
_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)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

@ -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_, &region, &regionSize, 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
} }

View File

@ -0,0 +1,27 @@
#if __has_feature(modules)
@import UIKit;
@import Foundation;
#else
#import "UIKit/UIKit.h"
#import "Foundation/Foundation.h"
#endif
#define DISPATCH_ASYNC_START dispatch_async(dispatch_get_main_queue(), ^{
#define DISPATCH_ASYNC_CLOSE });
#define PT_TRACE_ME 0
extern int ptrace(int, pid_t, caddr_t, int);
#define CS_DEBUGGED 0x10000000
extern int csops(
pid_t pid,
unsigned int ops,
void *useraddr,
size_t usersize
);
extern BOOL getEntitlementValue(NSString *key);
extern BOOL isJITEnabled(void);
#define DLOG(format, ...) ShowAlert(@"DEBUG", [NSString stringWithFormat:@"\n %s [Line %d] \n %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:format, ##__VA_ARGS__]])
void ShowAlert(NSString* title, NSString* message, _Bool* showok);

View File

@ -0,0 +1,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
}
}

View File

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

View File

@ -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
var engine = engine
// If no engine passed, use device engine
if engine == nil {
// Create and start the haptic engine // Create and start the haptic engine
if hapticEngine == nil { let engine = try CHHapticEngine()
hapticEngine = try CHHapticEngine() try engine.start()
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 }

View File

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

View File

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

View File

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

View File

@ -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
} }
} }
@ -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 {
@ -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,27 +317,23 @@ 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: ["\(index == 0 ? "--input-id-handheld" : "--input-id-\(index + 1)")", inputId])
} else {
args.append(contentsOf: ["--input-id-\(index + 1)", inputId]) args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
} }
} }
}
// Apped any additional arguments // Apped any additional arguments
args.append(contentsOf: config.additionalArgs) args.append(contentsOf: config.additionalArgs)
@ -381,29 +373,6 @@ class Ryujinx {
} }
} }
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,7 +461,7 @@ 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)
@ -500,18 +469,15 @@ class Ryujinx {
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")

View File

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

View File

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

View File

@ -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
@ -57,17 +56,25 @@ struct ContentView: View {
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,8 +306,7 @@ 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 = []
@ -299,6 +324,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) {
guard let game else { return } guard let game else { return }
@ -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])
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,10 +14,7 @@ struct GameInfoSheet: View {
var body: some View { var body: some View {
iOSNav { iOSNav {
List { VStack {
Section {}
header: {
VStack(alignment: .center) {
if let icon = game.icon { if let icon = game.icon {
Image(uiImage: icon) Image(uiImage: icon)
.resizable() .resizable()
@ -39,27 +36,23 @@ struct GameInfoSheet: View {
.frame(width: 150, height: 150) .frame(width: 150, height: 150)
.padding() .padding()
} }
VStack(alignment: .center) {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("**\(game.titleName)** | \(game.titleId.capitalized)") Text("**\(game.titleName)** | \(game.titleId.capitalized)")
.multilineTextAlignment(.center)
Text(game.developer) Text(game.developer)
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.padding(.vertical, 3) .padding(.vertical, 3)
}
.frame(maxWidth: .infinity)
}
Section { VStack(alignment: .leading, spacing: 5) {
HStack { Text("Information")
Text("**Version**") .font(.title2)
Spacer() .bold()
Text(game.version)
.foregroundStyle(Color.secondary) Text("**Version:** \(game.version)")
} Text("**Title ID:** \(game.titleId)")
HStack {
Text("**Title ID**")
.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")
} }
} }
Text("**Game Size:** \(fetchFileSize(for: game.fileURL) ?? 0) bytes")
Text("**File Type:** .\(getFileType(game.fileURL))")
Text("**Game URL:** \(trimGameURL(game.fileURL))")
}
}
Spacer() Spacer()
Text(game.titleId)
.foregroundStyle(Color.secondary)
}
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)
} }
.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"
} }
} }

View File

@ -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,9 +37,7 @@ 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) ||
@ -49,16 +45,17 @@ struct GameLibraryView: View {
} }
} }
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 {
LazyVStack(alignment: .leading, spacing: 20) {
if !isSearching {
Text("Games")
.font(.system(size: 34, weight: .bold))
.padding(.horizontal)
.padding(.top, 12)
}
if Ryujinx.shared.games.isEmpty { if Ryujinx.shared.games.isEmpty {
VStack(spacing: 16) { VStack(spacing: 16) {
Image(systemName: "gamecontroller.fill") Image(systemName: "gamecontroller.fill")
@ -75,38 +72,52 @@ struct GameLibraryView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.top, 40) .padding(.top, 40)
} else { } else {
if !isSearching && !realRecentGames.isEmpty { if !isSearching && !recentGames.isEmpty {
Section { VStack(alignment: .leading, spacing: 12) {
ForEach(realRecentGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
removeFromRecentGames(game)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
} header: {
Text("Recent") 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)
}
} }
Section { VStack(alignment: .leading, spacing: 12) {
Text("All Games")
.font(.title2.bold())
.padding(.horizontal)
LazyVStack(spacing: 2) {
ForEach(filteredGames) { game in ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo) GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
.onTapGesture {
addToRecentGames(game)
}
}
} }
} header: {
Text("Others")
} }
} else { } else {
LazyVStack(spacing: 2) {
ForEach(filteredGames) { game in ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo) GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
.onTapGesture {
addToRecentGames(game)
}
}
} }
} }
} }
} }
.navigationTitle("Games")
.navigationBarTitleDisplayMode(.large)
.onAppear { .onAppear {
loadRecentGames() loadRecentGames()
@ -131,8 +142,9 @@ struct GameLibraryView: View {
print(error) print(error)
} }
} }
}
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarLeading) {
Button { Button {
isSelectingGameFile.toggle() isSelectingGameFile.toggle()
} label: { } label: {
@ -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,8 +292,9 @@ 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)
@ -302,11 +305,6 @@ 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,6 +325,7 @@ 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
@ -369,21 +368,64 @@ extension Game: Codable {
} }
} }
// 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 // 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
@ -427,7 +469,9 @@ struct GameListRow: View {
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.opacity(0.8) .opacity(0.8)
} }
} .padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBackground))
.contextMenu { .contextMenu {
Section { Section {
Button { Button {
@ -439,33 +483,11 @@ struct GameListRow: View {
Button { Button {
gameInfo = game gameInfo = game
isViewingGameInfo.toggle() isViewingGameInfo.toggle()
if game.titleName.lowercased() == "portal" {
gamepo = true
} else if game.titleName.lowercased() == "portal 2" {
gamepo = true
}
} label: { } label: {
Label("Game Info", systemImage: "info.circle") Label("Game Info", systemImage: "info.circle")
} }
} }
Section {
Button {
gameInfo = game
isSelectingGameUpdate.toggle()
} label: {
Label("Game Update Manager", systemImage: "chevron.up.circle")
}
Button {
gameInfo = game
isSelectingGameDLC.toggle()
} label: {
Label("Game DLC Manager", systemImage: "plus.viewfinder")
}
}
Section { Section {
Button(role: .destructive) { Button(role: .destructive) {
gametoDelete = game gametoDelete = game
@ -475,6 +497,8 @@ struct GameListRow: View {
} }
} }
} }
}
.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 {
} }
} }
} }

View File

@ -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")
}.tint(.blue)
Toggle(isOn: $config.listinputids) {
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 {
labelWithIcon("JIT Acquisition: \(isJITEnabled() ? "Acquired" : "Not Acquired" )", iconName: "bolt.fill")
if #unavailable(iOS 17) {
Toggle(isOn: $windowCode) { Toggle(isOn: $windowCode) {
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle") 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) {
if iconName.hasSuffix(".svg"){
if let flipimage, flipimage {
SVGView(svgName: iconName, color: .blue)
.symbolRenderingMode(.hierarchical)
.frame(width: 20, height: 20)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
} else {
SVGView(svgName: iconName, color: .blue)
.symbolRenderingMode(.hierarchical)
.frame(width: 20, height: 20)
}
} else if !iconName.isEmpty {
Image(systemName: iconName) Image(systemName: iconName)
.symbolRenderingMode(.hierarchical) .symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue) .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
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +0,0 @@
//
// bridge.h
// RyujinxBridge
//
// Created by Daniil Vinogradov on 17/02/2025.
//
void (*messageDelegate)(const char*);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,14 +115,6 @@ 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 unsafe void CopyStringToFixedArray(string source, byte* destination, int length) private static void CopyStringToFixedArray(string source, byte* destination, int length)
{ {
var span = new Span<byte>(destination, length); var span = new Span<byte>(destination, length);
span.Clear(); span.Clear();
Encoding.UTF8.GetBytes(source, span); Encoding.UTF8.GetBytes(source, span);
} }
} }
}
} }

View File

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