forked from MeloNX/MeloNX
Compare commits
No commits in common. "XC-ios-ht" and "1.7.0" have entirely different histories.
@ -1,49 +0,0 @@
|
|||||||
name: Update apps.json on new release
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update:
|
|
||||||
runs-on: debian-trixie
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y jq
|
|
||||||
|
|
||||||
- name: Extract release data
|
|
||||||
id: release
|
|
||||||
run: |
|
|
||||||
echo "VERSION=${GITEA_REF_NAME}" >> $GITHUB_OUTPUT
|
|
||||||
echo "DESCRIPTION=$(echo '${GITEA_EVENT_RELEASE_BODY}' | jq -Rs .)" >> $GITHUB_OUTPUT
|
|
||||||
echo "DATE=$(date '+%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
IPA_URL=$(echo '${GITEA_EVENT_RELEASE_ASSETS}' | jq -r '.[0].browser_download_url')
|
|
||||||
echo "DOWNLOAD_URL=$IPA_URL" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Update apps.json
|
|
||||||
run: |
|
|
||||||
jq --arg version "${{ steps.release.outputs.VERSION }}" \
|
|
||||||
--arg buildVersion "1" \
|
|
||||||
--arg date "${{ steps.release.outputs.DATE }}" \
|
|
||||||
--arg localizedDescription "${{ steps.release.outputs.DESCRIPTION }}" \
|
|
||||||
--arg downloadURL "${{ steps.release.outputs.DOWNLOAD_URL }}" \
|
|
||||||
'.apps[0].versions |= [{"version": $version, "buildVersion": $buildVersion, "date": $date, "localizedDescription": $localizedDescription, "downloadURL": $downloadURL, "minOSVersion": "15.0"}]' \
|
|
||||||
apps.json > tmp.json && mv tmp.json apps.json
|
|
||||||
|
|
||||||
- name: Commit and push
|
|
||||||
run: |
|
|
||||||
git config user.name "gitea-actions"
|
|
||||||
git config user.email "gitea-actions@localhost"
|
|
||||||
git add apps.json
|
|
||||||
git commit -m "Update apps.json for release ${{ steps.release.outputs.VERSION }}"
|
|
||||||
git push
|
|
||||||
env:
|
|
||||||
GIT_AUTHOR_NAME: gitea-actions
|
|
||||||
GIT_AUTHOR_EMAIL: gitea-actions@localhost
|
|
||||||
GIT_COMMITTER_NAME: gitea-actions
|
|
||||||
GIT_COMMITTER_EMAIL: gitea-actions@localhost
|
|
@ -1,12 +1,3 @@
|
|||||||
Currently licensed under the GNU AFFERO GENERAL PUBLIC LICENSE version 3, or any later version, at your choice.
|
|
||||||
You may obtain a copy of the license at <https://gnu.org/>
|
|
||||||
|
|
||||||
Copyright (c) Rhajune Park and contributors, 2025
|
|
||||||
|
|
||||||
For copyright infringement claims, please contact abuse@pythonplayer123.dev for expedited processing
|
|
||||||
|
|
||||||
Previously licensed under the MeloNX License.
|
|
||||||
|
|
||||||
MeloNX License
|
MeloNX License
|
||||||
|
|
||||||
Copyright (c) MeloNX Team and Contributors
|
Copyright (c) MeloNX Team and Contributors
|
||||||
|
49
source.json
49
source.json
@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "MeloNX",
|
|
||||||
"subtitle": "A source for the MeloNX Application",
|
|
||||||
"description": "Welcome to the MeloNX source! The latest download for MeloNX.",
|
|
||||||
"iconURL": "https://git.743378673.xyz/CycloKid/assets/media/branch/main/Melo/AppIcons/MeloNX.png",
|
|
||||||
"headerURL": "https://cdn.discordapp.com/attachments/1320760161836466257/1331670540447912090/melon-x-not-melo-nx-amiright-guys.png?ex=67f556d6&is=67f40556&hm=71be8f109a14f1c47d8f4965aa017bccb5617962b7a9f5cdfb936a5a8135dad7&",
|
|
||||||
"website": "https://MeloNX.org",
|
|
||||||
"tintColor": "#AE34EB",
|
|
||||||
"featuredApps": [
|
|
||||||
"com.stossy11.MeloNX"
|
|
||||||
],
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"name": "MeloNX",
|
|
||||||
"bundleIdentifier": "com.stossy11.MeloNX",
|
|
||||||
"developerName": "Stossy11",
|
|
||||||
"subtitle": "An NX Emulator.",
|
|
||||||
"localizedDescription": "MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices. Developed from the ground up, MeloNX is open-source and available on a custom Gitea server under the MeloNX license (Based on MIT) (requires increased memory limit)",
|
|
||||||
"iconURL": "https://git.743378673.xyz/CycloKid/assets/media/branch/main/Melo/AppIcons/MeloNX.png",
|
|
||||||
"tintColor": "#AE34EB",
|
|
||||||
"category": "games",
|
|
||||||
"screenshots": [
|
|
||||||
"https://git.743378673.xyz/stossy11/screenshots/raw/branch/main/IMG_0380.PNG",
|
|
||||||
"https://git.743378673.xyz/stossy11/screenshots/raw/branch/main/IMG_0381.PNG"
|
|
||||||
],
|
|
||||||
"versions": [
|
|
||||||
{
|
|
||||||
"version": "1.7.0",
|
|
||||||
"buildVersion": "1",
|
|
||||||
"date": "2025-04-08",
|
|
||||||
"localizedDescription": "First AltStore release!",
|
|
||||||
"downloadURL": "https://git.743378673.xyz/MeloNX/MeloNX/releases/download/1.7.0/MeloNX.ipa",
|
|
||||||
"size": 79821,
|
|
||||||
"minOSVersion": "15.0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"appPermissions": {
|
|
||||||
"entitlements": [
|
|
||||||
"get-task-allow",
|
|
||||||
"com.apple.developer.kernel.increased-memory-limit"
|
|
||||||
],
|
|
||||||
"privacy": {
|
|
||||||
"NSPhotoLibraryAddUsageDescription": "MeloNX needs access to your Photo Library in order to save screenshots."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"news": []
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ 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;
|
||||||
@ -15,52 +16,73 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
static partial class JitCache
|
static partial class JitCache
|
||||||
{
|
{
|
||||||
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
|
||||||
private static readonly int _pageMask = _pageSize - 1;
|
private static readonly int _pageMask = _pageSize - 4;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 2047 * 1024 * 1024;
|
private const int CacheSize = 128 * 1024 * 1024;
|
||||||
private const int CacheSizeIOS = 1024 * 1024 * 1024;
|
private const int CacheSizeIOS = 128 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
private static ReservedRegion _jitRegion;
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
|
||||||
private static CacheMemoryAllocator _cacheAllocator;
|
private static List<CacheMemoryAllocator> _cacheAllocators = [];
|
||||||
|
|
||||||
private static readonly List<CacheEntry> _cacheEntries = new();
|
private static readonly List<CacheEntry> _cacheEntries = new();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
public static void Initialize(IJitMemoryAllocator allocator)
|
public static void Initialize(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
if (_initialized)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (_initialized)
|
if (_initialized)
|
||||||
{
|
{
|
||||||
return;
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
// JitUnwindWindows.RemoveFunctionTableHandler(
|
||||||
|
// _jitRegions[0].Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _jitRegions.Count; i++)
|
||||||
|
{
|
||||||
|
_jitRegions[i].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_jitRegions.Clear();
|
||||||
|
_cacheAllocators.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
|
_activeRegionIndex = 0;
|
||||||
|
|
||||||
|
var firstRegion = new ReservedRegion(allocator, CacheSize);
|
||||||
|
_jitRegions.Add(firstRegion);
|
||||||
|
|
||||||
|
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
|
||||||
|
_cacheAllocators.Add(firstCacheAllocator);
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
|
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
|
JitUnwindWindows.InstallFunctionTableHandler(
|
||||||
|
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@ -73,7 +95,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
while (_deferredRxProtect.TryDequeue(out var result))
|
while (_deferredRxProtect.TryDequeue(out var result))
|
||||||
{
|
{
|
||||||
ReprotectAsExecutable(result.funcOffset, result.length);
|
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||||
|
|
||||||
|
ReprotectAsExecutable(targetRegion, result.funcOffset, result.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +111,8 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
int funcOffset = Allocate(code.Length, deferProtect);
|
int funcOffset = Allocate(code.Length, deferProtect);
|
||||||
|
|
||||||
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
|
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||||
|
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
|
||||||
|
|
||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
@ -98,8 +123,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsExecutable(funcOffset, code.Length);
|
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||||
|
|
||||||
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,9 +139,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsWritable(funcOffset, code.Length);
|
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(funcOffset, code.Length);
|
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@ -139,41 +163,50 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
return;
|
// return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
Debug.Assert(_initialized);
|
foreach (var region in _jitRegions)
|
||||||
|
|
||||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
|
||||||
|
|
||||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
|
||||||
{
|
{
|
||||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||||
_cacheEntries.RemoveAt(entryIndex);
|
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||||
|
|
||||||
|
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||||
|
{
|
||||||
|
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
|
||||||
|
_cacheEntries.RemoveAt(entryIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsWritable(int offset, int size)
|
private static void ReprotectAsWritable(ReservedRegion region, 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;
|
||||||
|
|
||||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsExecutable(int offset, int size)
|
private static void ReprotectAsExecutable(ReservedRegion region, 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;
|
||||||
|
|
||||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
region.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)
|
||||||
@ -187,18 +220,33 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
alignment = 0x4000;
|
alignment = 0x4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(ref codeSize, alignment);
|
||||||
|
|
||||||
Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
|
if (allocOffset >= 0)
|
||||||
|
|
||||||
if (allocOffset < 0)
|
|
||||||
{
|
{
|
||||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||||
|
return allocOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
int exhaustedRegion = _activeRegionIndex;
|
||||||
|
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
|
||||||
|
_jitRegions.Add(newRegion);
|
||||||
|
_activeRegionIndex = _jitRegions.Count - 1;
|
||||||
|
|
||||||
return allocOffset;
|
int newRegionNumber = _activeRegionIndex;
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize)} Total Allocation).");
|
||||||
|
|
||||||
|
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
|
||||||
|
|
||||||
|
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].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)
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
/* End PBXAggregateTarget section */
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||||
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
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 */; };
|
||||||
@ -128,6 +129,10 @@
|
|||||||
"Dependencies/Dynamic Libraries/libavutil.dylib" = (
|
"Dependencies/Dynamic Libraries/libavutil.dylib" = (
|
||||||
CodeSignOnCopy,
|
CodeSignOnCopy,
|
||||||
);
|
);
|
||||||
|
Dependencies/XCFrameworks/MoltenVK.xcframework = (
|
||||||
|
CodeSignOnCopy,
|
||||||
|
RemoveHeadersOnCopy,
|
||||||
|
);
|
||||||
Dependencies/XCFrameworks/SDL2.xcframework = (
|
Dependencies/XCFrameworks/SDL2.xcframework = (
|
||||||
CodeSignOnCopy,
|
CodeSignOnCopy,
|
||||||
RemoveHeadersOnCopy,
|
RemoveHeadersOnCopy,
|
||||||
@ -181,6 +186,7 @@
|
|||||||
Dependencies/XCFrameworks/libswresample.xcframework,
|
Dependencies/XCFrameworks/libswresample.xcframework,
|
||||||
Dependencies/XCFrameworks/libswscale.xcframework,
|
Dependencies/XCFrameworks/libswscale.xcframework,
|
||||||
Dependencies/XCFrameworks/libteakra.xcframework,
|
Dependencies/XCFrameworks/libteakra.xcframework,
|
||||||
|
Dependencies/XCFrameworks/MoltenVK.xcframework,
|
||||||
Dependencies/XCFrameworks/SDL2.xcframework,
|
Dependencies/XCFrameworks/SDL2.xcframework,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -197,6 +203,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
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 */,
|
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
|
||||||
@ -294,6 +301,7 @@
|
|||||||
);
|
);
|
||||||
name = MeloNX;
|
name = MeloNX;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */,
|
||||||
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
|
||||||
);
|
);
|
||||||
productName = MeloNX;
|
productName = MeloNX;
|
||||||
@ -385,6 +393,7 @@
|
|||||||
mainGroup = 4E80A9842CD6F54500029585;
|
mainGroup = 4E80A9842CD6F54500029585;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */,
|
||||||
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 56;
|
preferredProjectObjectVersion = 56;
|
||||||
@ -643,7 +652,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 = "";
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = NO;
|
ENABLE_TESTABILITY = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@ -712,24 +721,6 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(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 = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -745,7 +736,7 @@
|
|||||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -872,50 +863,6 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(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 = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -1008,24 +955,6 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(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 = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -1041,7 +970,7 @@
|
|||||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -1168,50 +1097,6 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
|
||||||
"$(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 = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
@ -1413,6 +1298,14 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.5.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
|
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/mchoe/SwiftSVG";
|
repositoryURL = "https://github.com/mchoe/SwiftSVG";
|
||||||
@ -1424,6 +1317,11 @@
|
|||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */;
|
||||||
|
productName = SwiftUIJoystick;
|
||||||
|
};
|
||||||
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
|
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "d611b071fbe94fdc9900a07a218340eab4ce2c3c7168bf6542f2830c0400a72b",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "swiftsvg",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/mchoe/SwiftSVG",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swiftuijoystick",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/michael94ellis/SwiftUIJoystick",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "5bd303cdafb369a70a45c902538b42dd3c5f4d65",
|
||||||
|
"version" : "1.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<array/>
|
||||||
|
</plist>
|
Binary file not shown.
Binary file not shown.
BIN
src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/ls.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
BIN
src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/ls.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1620"
|
LastUpgradeVersion = "1620"
|
||||||
version = "2.0">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES"
|
||||||
@ -62,11 +62,10 @@
|
|||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugXPCServices = "NO"
|
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
enableGPUValidationMode = "1"
|
||||||
allowLocationSimulation = "YES"
|
allowLocationSimulation = "YES"
|
||||||
queueDebuggingEnabled = "No"
|
viewDebuggingEnabled = "No"
|
||||||
consoleMode = "0"
|
consoleMode = "0"
|
||||||
structuredConsoleMode = "2">
|
structuredConsoleMode = "2">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
|
@ -31,12 +31,12 @@ func SecTaskCopyValuesForEntitlements(
|
|||||||
|
|
||||||
func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
|
func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
|
||||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
guard let task = SecTaskCreateFromSelf(nil) else {
|
||||||
// print("Failed to create SecTask")
|
print("Failed to create SecTask")
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let entitlements = SecTaskCopyValuesForEntitlements(task, ents as CFArray, nil) else {
|
guard let entitlements = SecTaskCopyValuesForEntitlements(task, ents as CFArray, nil) else {
|
||||||
// print("Failed to get entitlements")
|
print("Failed to get entitlements")
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,12 +45,12 @@ func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
|
|||||||
|
|
||||||
func checkAppEntitlement(_ ent: String) -> Bool {
|
func checkAppEntitlement(_ ent: String) -> Bool {
|
||||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
guard let task = SecTaskCreateFromSelf(nil) else {
|
||||||
// print("Failed to create SecTask")
|
print("Failed to create SecTask")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let entitlements = SecTaskCopyValueForEntitlement(task, ent as NSString, nil) else {
|
guard let entitlements = SecTaskCopyValueForEntitlement(task, ent as NSString, nil) else {
|
||||||
// print("Failed to get entitlements")
|
print("Failed to get entitlements")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ char* installed_firmware_version();
|
|||||||
|
|
||||||
void set_native_window(void *layerPtr);
|
void set_native_window(void *layerPtr);
|
||||||
|
|
||||||
void stop_emulation(bool shouldPause);
|
void stop_emulation();
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if result != KERN_SUCCESS {
|
if result != KERN_SUCCESS {
|
||||||
// print("Failed to reach \(address)")
|
print("Failed to reach \(address)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ func enableJITEB() {
|
|||||||
|
|
||||||
func enableJITEBRequest() {
|
func enableJITEBRequest() {
|
||||||
let pid = Int(getpid())
|
let pid = Int(getpid())
|
||||||
// print(pid)
|
print(pid)
|
||||||
|
|
||||||
let address = URL(string: "http://[fd00::]:9172/attach/\(pid)")!
|
let address = URL(string: "http://[fd00::]:9172/attach/\(pid)")!
|
||||||
var request = URLRequest(url: address)
|
var request = URLRequest(url: address)
|
||||||
@ -90,7 +90,7 @@ func pingSite(host: String = "http://[fd00::]:9172/hello", completion: @escaping
|
|||||||
|
|
||||||
let task = session.dataTask(with: request) { _, response, error in
|
let task = session.dataTask(with: request) { _, response, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
// print("Ping failed: \(error.localizedDescription)")
|
print("Ping failed: \(error.localizedDescription)")
|
||||||
completion(false)
|
completion(false)
|
||||||
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
|
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
|
||||||
completion(true)
|
completion(true)
|
||||||
@ -140,12 +140,12 @@ func showLaunchAppAlert(jsonData: Data, in viewController: UIViewController) {
|
|||||||
viewController.present(alert, animated: true)
|
viewController.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// print("Hopefully JIT is enabled now...")
|
print("Hopefully JIT is enabled now...")
|
||||||
Ryujinx.shared.ryuIsJITEnabled()
|
Ryujinx.shared.ryuIsJITEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
// print(String(data: jsonData, encoding: .utf8))
|
print(String(data: jsonData, encoding: .utf8))
|
||||||
let alert = UIAlertController(title: "Decoding Error", message: error.localizedDescription, preferredStyle: .alert)
|
let alert = UIAlertController(title: "Decoding Error", message: error.localizedDescription, preferredStyle: .alert)
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||||
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
//
|
|
||||||
// EnableJIT.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 10/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Network
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
func enableJITStik() {
|
|
||||||
let bundleid = Bundle.main.bundleIdentifier ?? "Unknown"
|
|
||||||
|
|
||||||
let address = URL(string: "stikjit://enable-jit?bundle-id=\(bundleid)")!
|
|
||||||
if UIApplication.shared.canOpenURL(address) {
|
|
||||||
UIApplication.shared.open(address)
|
|
||||||
}
|
|
||||||
}
|
|
@ -49,39 +49,39 @@ class NativeController: Hashable {
|
|||||||
// Update joystick state here
|
// Update joystick state here
|
||||||
},
|
},
|
||||||
SetPlayerIndex: { userdata, playerIndex in
|
SetPlayerIndex: { userdata, playerIndex in
|
||||||
// print("Player index set to \(playerIndex)")
|
print("Player index set to \(playerIndex)")
|
||||||
},
|
},
|
||||||
Rumble: { userdata, lowFreq, highFreq in
|
Rumble: { userdata, lowFreq, highFreq in
|
||||||
// print("Rumble with \(lowFreq), \(highFreq)")
|
print("Rumble with \(lowFreq), \(highFreq)")
|
||||||
guard let userdata else { return 0 }
|
guard let userdata else { return 0 }
|
||||||
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
|
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
|
||||||
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq), engine: _self.controllerHaptics)
|
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq), engine: _self.controllerHaptics)
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
||||||
// print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
SetLED: { userdata, red, green, blue in
|
SetLED: { userdata, red, green, blue in
|
||||||
// print("Set LED to RGB(\(red), \(green), \(blue))")
|
print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
SendEffect: { userdata, data, size in
|
SendEffect: { userdata, data, size in
|
||||||
// print("Effect sent with size \(size)")
|
print("Effect sent with size \(size)")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
||||||
if instanceID < 0 {
|
if instanceID < 0 {
|
||||||
// print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
if controller == nil {
|
if controller == nil {
|
||||||
// print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,10 +89,10 @@ class NativeController: Hashable {
|
|||||||
guard let gamepad = nativeController.extendedGamepad
|
guard let gamepad = nativeController.extendedGamepad
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.buttonA, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .B : .A)
|
setupButtonChangeListener(gamepad.buttonA, for: .A)
|
||||||
setupButtonChangeListener(gamepad.buttonB, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .A : .B)
|
setupButtonChangeListener(gamepad.buttonB, for: .B)
|
||||||
setupButtonChangeListener(gamepad.buttonX, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .Y : .X)
|
setupButtonChangeListener(gamepad.buttonX, for: .X)
|
||||||
setupButtonChangeListener(gamepad.buttonY, for: UserDefaults.standard.bool(forKey: "swapBandA") ? .X : .Y)
|
setupButtonChangeListener(gamepad.buttonY, for: .Y)
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
|
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
|
||||||
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
|
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
|
||||||
@ -139,7 +139,7 @@ class NativeController: Hashable {
|
|||||||
|
|
||||||
func setupTriggerChangeListener(_ button: GCControllerButtonInput, for key: ThumbstickType) {
|
func setupTriggerChangeListener(_ button: GCControllerButtonInput, for key: ThumbstickType) {
|
||||||
button.valueChangedHandler = { [unowned self] _, value, pressed in
|
button.valueChangedHandler = { [unowned self] _, value, pressed in
|
||||||
// // print("Value: \(value), Is pressed: \(pressed)")
|
// print("Value: \(value), Is pressed: \(pressed)")
|
||||||
let axis: SDL_GameControllerAxis = (key == .left) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
let axis: SDL_GameControllerAxis = (key == .left) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||||
let scaledValue = Sint16(value * 32767.0)
|
let scaledValue = Sint16(value * 32767.0)
|
||||||
updateAxisValue(value: scaledValue, forAxis: axis)
|
updateAxisValue(value: scaledValue, forAxis: axis)
|
||||||
@ -177,7 +177,7 @@ class NativeController: Hashable {
|
|||||||
try highFreqPlayer.start(atTime: 0.2)
|
try highFreqPlayer.start(atTime: 0.2)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error creating haptic patterns: \(error)")
|
print("Error creating haptic patterns: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ class NativeController: Hashable {
|
|||||||
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
||||||
guard controller != nil else { return }
|
guard controller != nil else { return }
|
||||||
|
|
||||||
// // print("Button: \(button.rawValue) {state: \(state)}")
|
// print("Button: \(button.rawValue) {state: \(state)}")
|
||||||
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
||||||
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||||
let value: Int = (state == 1) ? 32767 : 0
|
let value: Int = (state == 1) ? 32767 : 0
|
||||||
|
@ -41,39 +41,39 @@ class VirtualController {
|
|||||||
// Update joystick state here
|
// Update joystick state here
|
||||||
},
|
},
|
||||||
SetPlayerIndex: { userdata, playerIndex in
|
SetPlayerIndex: { userdata, playerIndex in
|
||||||
// print("Player index set to \(playerIndex)")
|
print("Player index set to \(playerIndex)")
|
||||||
},
|
},
|
||||||
Rumble: { userdata, lowFreq, highFreq in
|
Rumble: { userdata, lowFreq, highFreq in
|
||||||
// print("Rumble with \(lowFreq), \(highFreq)")
|
print("Rumble with \(lowFreq), \(highFreq)")
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
|
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
||||||
// print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
print("Trigger rumble with \(leftRumble), \(rightRumble)")
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
SetLED: { userdata, red, green, blue in
|
SetLED: { userdata, red, green, blue in
|
||||||
// print("Set LED to RGB(\(red), \(green), \(blue))")
|
print("Set LED to RGB(\(red), \(green), \(blue))")
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
SendEffect: { userdata, data, size in
|
SendEffect: { userdata, data, size in
|
||||||
// print("Effect sent with size \(size)")
|
print("Effect sent with size \(size)")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
|
||||||
if instanceID < 0 {
|
if instanceID < 0 {
|
||||||
// print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
if controller == nil {
|
if controller == nil {
|
||||||
// print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ class VirtualController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let engine else {
|
guard let engine else {
|
||||||
return // print("Error creating haptic patterns: hapticEngine is nil")
|
return print("Error creating haptic patterns: hapticEngine is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
||||||
@ -117,7 +117,7 @@ class VirtualController {
|
|||||||
try highFreqPlayer.start(atTime: 0)
|
try highFreqPlayer.start(atTime: 0)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error creating haptic patterns: \(error)")
|
print("Error creating haptic patterns: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +131,10 @@ class VirtualController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) {
|
func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) {
|
||||||
let scaledX = Int16(min(32767.0, max(-32768.0, x * 32767.0)))
|
let scaleFactor = 32767.0 / 160
|
||||||
let scaledY = Int16(min(32767.0, max(-32768.0, y * 32767.0)))
|
|
||||||
|
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 {
|
if stick == .right {
|
||||||
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
|
||||||
@ -146,7 +148,7 @@ class VirtualController {
|
|||||||
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
|
||||||
guard controller != nil else { return }
|
guard controller != nil else { return }
|
||||||
|
|
||||||
// // print("Button: \(button.rawValue) {state: \(state)}")
|
print("Button: \(button.rawValue) {state: \(state)}")
|
||||||
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
|
||||||
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||||
let value: Int = (state == 1) ? 32767 : 0
|
let value: Int = (state == 1) ? 32767 : 0
|
||||||
|
@ -35,8 +35,8 @@ class MemoryUsageMonitor: ObservableObject {
|
|||||||
memoryUsage = taskInfo.phys_footprint
|
memoryUsage = taskInfo.phys_footprint
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// print("Error with task_info(): " +
|
print("Error with task_info(): " +
|
||||||
// (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
(String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class MTLHud {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toggle() {
|
func toggle() {
|
||||||
// print(UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED"))
|
print(UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED"))
|
||||||
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||||
enable()
|
enable()
|
||||||
} else {
|
} else {
|
||||||
@ -44,12 +44,12 @@ class MTLHud {
|
|||||||
let path = "/usr/lib/libMTLHud.dylib"
|
let path = "/usr/lib/libMTLHud.dylib"
|
||||||
|
|
||||||
if dlopen(path, RTLD_NOW) != nil {
|
if dlopen(path, RTLD_NOW) != nil {
|
||||||
// print("Library loaded from \(path)")
|
print("Library loaded from \(path)")
|
||||||
canMetalHud = true
|
canMetalHud = true
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
if let error = String(validatingUTF8: dlerror()) {
|
if let error = String(validatingUTF8: dlerror()) {
|
||||||
// print("Error loading library: \(error)")
|
print("Error loading library: \(error)")
|
||||||
}
|
}
|
||||||
canMetalHud = false
|
canMetalHud = false
|
||||||
return false
|
return false
|
||||||
|
@ -11,93 +11,6 @@ import GameController
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Metal
|
import Metal
|
||||||
|
|
||||||
class LogCapture {
|
|
||||||
static let shared = LogCapture()
|
|
||||||
|
|
||||||
private var stdoutPipe: Pipe?
|
|
||||||
private var stderrPipe: Pipe?
|
|
||||||
private let originalStdout: Int32
|
|
||||||
private let originalStderr: Int32
|
|
||||||
|
|
||||||
var capturedLogs: [String] = [] {
|
|
||||||
didSet {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
NotificationCenter.default.post(name: .newLogCaptured, object: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
originalStdout = dup(STDOUT_FILENO)
|
|
||||||
originalStderr = dup(STDERR_FILENO)
|
|
||||||
startCapturing()
|
|
||||||
}
|
|
||||||
|
|
||||||
func startCapturing() {
|
|
||||||
stdoutPipe = Pipe()
|
|
||||||
stderrPipe = Pipe()
|
|
||||||
|
|
||||||
redirectOutput(to: stdoutPipe!, fileDescriptor: STDOUT_FILENO)
|
|
||||||
redirectOutput(to: stderrPipe!, fileDescriptor: STDERR_FILENO)
|
|
||||||
|
|
||||||
setupReadabilityHandler(for: stdoutPipe!, isStdout: true)
|
|
||||||
setupReadabilityHandler(for: stderrPipe!, isStdout: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopCapturing() {
|
|
||||||
dup2(originalStdout, STDOUT_FILENO)
|
|
||||||
dup2(originalStderr, STDERR_FILENO)
|
|
||||||
|
|
||||||
stdoutPipe?.fileHandleForReading.readabilityHandler = nil
|
|
||||||
stderrPipe?.fileHandleForReading.readabilityHandler = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private func redirectOutput(to pipe: Pipe, fileDescriptor: Int32) {
|
|
||||||
dup2(pipe.fileHandleForWriting.fileDescriptor, fileDescriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupReadabilityHandler(for pipe: Pipe, isStdout: Bool) {
|
|
||||||
pipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
|
|
||||||
let data = fileHandle.availableData
|
|
||||||
let originalFD = isStdout ? self?.originalStdout : self?.originalStderr
|
|
||||||
write(originalFD ?? STDOUT_FILENO, (data as NSData).bytes, data.count)
|
|
||||||
|
|
||||||
if let logString = String(data: data, encoding: .utf8),
|
|
||||||
let cleanedLog = self?.cleanLog(logString), !cleanedLog.isEmpty {
|
|
||||||
self?.capturedLogs.append(cleanedLog)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func cleanLog(_ raw: String) -> String? {
|
|
||||||
let lines = raw.split(separator: "\n")
|
|
||||||
let filteredLines = lines.filter { line in
|
|
||||||
!line.contains("SwiftUI") &&
|
|
||||||
!line.contains("ForEach") &&
|
|
||||||
!line.contains("VStack") &&
|
|
||||||
!line.contains("Invalid frame dimension (negative or non-finite).")
|
|
||||||
}
|
|
||||||
|
|
||||||
let cleaned = filteredLines.map { line -> String in
|
|
||||||
if let tabRange = line.range(of: "\t") {
|
|
||||||
return line[tabRange.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
}
|
|
||||||
return line.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
}.joined(separator: "\n")
|
|
||||||
|
|
||||||
return cleaned.isEmpty ? nil : cleaned.replacingOccurrences(of: "\n\n", with: "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
stopCapturing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension Notification.Name {
|
|
||||||
static let newLogCaptured = Notification.Name("newLogCaptured")
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Controller: Identifiable, Hashable {
|
struct Controller: Identifiable, Hashable {
|
||||||
var id: String
|
var id: String
|
||||||
var name: String
|
var name: String
|
||||||
@ -125,7 +38,6 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
@Published var controllerMap: [Controller] = []
|
@Published var controllerMap: [Controller] = []
|
||||||
@Published var metalLayer: CAMetalLayer? = nil
|
@Published var metalLayer: CAMetalLayer? = nil
|
||||||
@Published var isPortrait = false
|
|
||||||
@Published var firmwareversion = "0"
|
@Published var firmwareversion = "0"
|
||||||
@Published var emulationUIView: MeloMTKView? = nil
|
@Published var emulationUIView: MeloMTKView? = nil
|
||||||
@Published var config: Ryujinx.Configuration? = nil
|
@Published var config: Ryujinx.Configuration? = nil
|
||||||
@ -133,7 +45,7 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
@Published var defMLContentSize: CGFloat?
|
@Published var defMLContentSize: CGFloat?
|
||||||
|
|
||||||
var thread: Thread = Thread { }
|
var thread: Thread!
|
||||||
|
|
||||||
@Published var jitenabled = false
|
@Published var jitenabled = false
|
||||||
|
|
||||||
@ -237,7 +149,6 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
|
||||||
thread = Thread { [self] in
|
thread = Thread { [self] in
|
||||||
|
|
||||||
isRunning = true
|
isRunning = true
|
||||||
@ -268,98 +179,15 @@ class Ryujinx : ObservableObject {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
self.isRunning = false
|
self.isRunning = false
|
||||||
Thread.sleep(forTimeInterval: 0.3)
|
Self.log("Emulation failed to start: \(error)")
|
||||||
let logs = LogCapture.shared.capturedLogs
|
|
||||||
let parsedLogs = extractExceptionInfo(logs)
|
|
||||||
if let parsedLogs {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let result = Array(logs.suffix(from: parsedLogs.lineIndex))
|
|
||||||
|
|
||||||
LogCapture.shared.capturedLogs = Array(LogCapture.shared.capturedLogs.prefix(upTo: parsedLogs.lineIndex))
|
|
||||||
let dateFormatter = DateFormatter()
|
|
||||||
dateFormatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
|
||||||
let currentDate = Date()
|
|
||||||
let dateString = dateFormatter.string(from: currentDate)
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent("StackTrace").appendingPathComponent("StackTrace-\(dateString).txt").path
|
|
||||||
|
|
||||||
self.saveArrayAsTextFile(strings: result, filePath: path)
|
|
||||||
|
|
||||||
|
|
||||||
presentAlert(title: "MeloNX Crashed!", message: parsedLogs.exceptionType + ": " + parsedLogs.message) {
|
|
||||||
|
|
||||||
assert(true, parsedLogs.exceptionType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
presentAlert(title: "MeloNX Crashed!", message: "Unknown Error") {
|
|
||||||
assert(true, "Exception was not detected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.qualityOfService = .userInteractive
|
thread.qualityOfService = .background
|
||||||
thread.name = "MeloNX"
|
thread.name = "MeloNX"
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveArrayAsTextFile(strings: [String], filePath: String) {
|
|
||||||
let text = strings.joined(separator: "\n")
|
|
||||||
|
|
||||||
let path = URL.documentsDirectory.appendingPathComponent("StackTrace").path
|
|
||||||
|
|
||||||
do {
|
|
||||||
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false)
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
try text.write(to: URL(fileURLWithPath: filePath), atomically: true, encoding: .utf8)
|
|
||||||
print("File saved successfully.")
|
|
||||||
} catch {
|
|
||||||
print("Error saving file: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ExceptionInfo {
|
|
||||||
let exceptionType: String
|
|
||||||
let message: String
|
|
||||||
let lineIndex: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractExceptionInfo(_ logs: [String]) -> ExceptionInfo? {
|
|
||||||
for i in (0..<logs.count).reversed() {
|
|
||||||
let line = logs[i]
|
|
||||||
let pattern = "([\\w\\.]+Exception): ([^\\s]+(?:\\s+[^\\s]+)*)"
|
|
||||||
|
|
||||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []),
|
|
||||||
let match = regex.firstMatch(in: line, options: [], range: NSRange(location: 0, length: line.count)) else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract exception type and message if pattern matches
|
|
||||||
if let exceptionTypeRange = Range(match.range(at: 1), in: line),
|
|
||||||
let messageRange = Range(match.range(at: 2), in: line) {
|
|
||||||
|
|
||||||
let exceptionType = String(line[exceptionTypeRange])
|
|
||||||
|
|
||||||
var message = String(line[messageRange])
|
|
||||||
if let atIndex = message.range(of: "\\s+at\\s+", options: .regularExpression) {
|
|
||||||
message = String(message[..<atIndex.lowerBound])
|
|
||||||
}
|
|
||||||
|
|
||||||
message = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
|
|
||||||
return ExceptionInfo(exceptionType: exceptionType, message: message, lineIndex: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func stop() throws {
|
func stop() throws {
|
||||||
guard isRunning else {
|
guard isRunning else {
|
||||||
@ -371,6 +199,7 @@ class Ryujinx : ObservableObject {
|
|||||||
self.emulationUIView = nil
|
self.emulationUIView = nil
|
||||||
self.metalLayer = nil
|
self.metalLayer = nil
|
||||||
|
|
||||||
|
stop_emulation()
|
||||||
thread.cancel()
|
thread.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +218,7 @@ class Ryujinx : ObservableObject {
|
|||||||
do {
|
do {
|
||||||
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
} catch {
|
} catch {
|
||||||
// print("Failed to create roms directory: \(error)")
|
print("Failed to create roms directory: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var games: [Game] = []
|
var games: [Game] = []
|
||||||
@ -414,13 +243,13 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
games.append(game)
|
games.append(game)
|
||||||
} catch {
|
} catch {
|
||||||
// print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return games
|
return games
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error loading games from roms folder: \(error)")
|
print("Error loading games from roms folder: \(error)")
|
||||||
return games
|
return games
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,24 +273,6 @@ class Ryujinx : ObservableObject {
|
|||||||
// 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
|
||||||
|
|
||||||
var model = ""
|
|
||||||
|
|
||||||
var systemInfo = utsname()
|
|
||||||
uname(&systemInfo)
|
|
||||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
|
||||||
model = machineMirror.children.reduce("") { identifier, element in
|
|
||||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
|
||||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
|
||||||
}
|
|
||||||
|
|
||||||
args.append(contentsOf: ["--device-model", model])
|
|
||||||
|
|
||||||
args.append(contentsOf: ["--device-display-name", UIDevice.modelName])
|
|
||||||
|
|
||||||
if checkAppEntitlement("com.apple.developer.kernel.increased-memory-limit") {
|
|
||||||
args.append("--has-memory-entitlement")
|
|
||||||
}
|
|
||||||
|
|
||||||
args.append(contentsOf: ["--system-language", config.language.rawValue])
|
args.append(contentsOf: ["--system-language", config.language.rawValue])
|
||||||
|
|
||||||
args.append(contentsOf: ["--system-region", config.regioncode.rawValue])
|
args.append(contentsOf: ["--system-region", config.regioncode.rawValue])
|
||||||
@ -572,7 +383,7 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
func installFirmware(firmwarePath: String) {
|
func installFirmware(firmwarePath: String) {
|
||||||
guard let cString = firmwarePath.cString(using: .utf8) else {
|
guard let cString = firmwarePath.cString(using: .utf8) else {
|
||||||
// print("Invalid firmware path")
|
print("Invalid firmware path")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,12 +399,12 @@ class Ryujinx : ObservableObject {
|
|||||||
guard let titleIdCString = titleId.cString(using: .utf8),
|
guard let titleIdCString = titleId.cString(using: .utf8),
|
||||||
let pathCString = path.cString(using: .utf8)
|
let pathCString = path.cString(using: .utf8)
|
||||||
else {
|
else {
|
||||||
// print("Invalid path")
|
print("Invalid path")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
let listPointer = get_dlc_nca_list(titleIdCString, pathCString)
|
let listPointer = get_dlc_nca_list(titleIdCString, pathCString)
|
||||||
// print("DLC parcing success: \(listPointer.success)")
|
print("DLC parcing success: \(listPointer.success)")
|
||||||
guard listPointer.success else { return [] }
|
guard listPointer.success else { return [] }
|
||||||
|
|
||||||
let list = Array(UnsafeBufferPointer(start: listPointer.items, count: Int(listPointer.size)))
|
let list = Array(UnsafeBufferPointer(start: listPointer.items, count: Int(listPointer.size)))
|
||||||
@ -645,7 +456,7 @@ class Ryujinx : ObservableObject {
|
|||||||
let guid = generateGamepadId(joystickIndex: i)
|
let guid = generateGamepadId(joystickIndex: i)
|
||||||
let name = String(cString: SDL_GameControllerName(controller))
|
let name = String(cString: SDL_GameControllerName(controller))
|
||||||
|
|
||||||
// print("Controller \(i): \(name), GUID: \(guid ?? "")")
|
print("Controller \(i): \(name), GUID: \(guid ?? "")")
|
||||||
|
|
||||||
guard let guid else {
|
guard let guid else {
|
||||||
SDL_GameControllerClose(controller)
|
SDL_GameControllerClose(controller)
|
||||||
@ -676,163 +487,33 @@ class Ryujinx : ObservableObject {
|
|||||||
do {
|
do {
|
||||||
if fileManager.fileExists(atPath: registeredFolder) {
|
if fileManager.fileExists(atPath: registeredFolder) {
|
||||||
try fileManager.removeItem(atPath: registeredFolder)
|
try fileManager.removeItem(atPath: registeredFolder)
|
||||||
// print("Folder removed successfully.")
|
print("Folder removed successfully.")
|
||||||
let version = fetchFirmwareVersion()
|
let version = fetchFirmwareVersion()
|
||||||
|
|
||||||
if version.isEmpty {
|
if version.isEmpty {
|
||||||
self.firmwareversion = "0"
|
self.firmwareversion = "0"
|
||||||
} else {
|
} else {
|
||||||
// print("Firmware eeeeee \(version)")
|
print("Firmware eeeeee \(version)")
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// print("Folder does not exist.")
|
print("Folder does not exist.")
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error removing folder: \(error)")
|
print("Error removing folder: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static func log(_ message: String) {
|
static func log(_ message: String) {
|
||||||
// print("[Ryujinx] \(message)")
|
print("[Ryujinx] \(message)")
|
||||||
}
|
|
||||||
|
|
||||||
public func updateOrientation() -> Bool {
|
|
||||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
||||||
let window = windowScene.windows.first {
|
|
||||||
return (window.bounds.size.height > window.bounds.size.width)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ryuIsJITEnabled() {
|
func ryuIsJITEnabled() {
|
||||||
jitenabled = isJITEnabled()
|
jitenabled = isJITEnabled()
|
||||||
|
print("JIT \(jitenabled)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public extension UIDevice {
|
|
||||||
static let modelName: 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)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity
|
|
||||||
#if os(iOS)
|
|
||||||
switch identifier {
|
|
||||||
case "iPod5,1": return "iPod touch (5th generation)"
|
|
||||||
case "iPod7,1": return "iPod touch (6th generation)"
|
|
||||||
case "iPod9,1": return "iPod touch (7th generation)"
|
|
||||||
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4"
|
|
||||||
case "iPhone4,1": return "iPhone 4s"
|
|
||||||
case "iPhone5,1", "iPhone5,2": return "iPhone 5"
|
|
||||||
case "iPhone5,3", "iPhone5,4": return "iPhone 5c"
|
|
||||||
case "iPhone6,1", "iPhone6,2": return "iPhone 5s"
|
|
||||||
case "iPhone7,2": return "iPhone 6"
|
|
||||||
case "iPhone7,1": return "iPhone 6 Plus"
|
|
||||||
case "iPhone8,1": return "iPhone 6s"
|
|
||||||
case "iPhone8,2": return "iPhone 6s Plus"
|
|
||||||
case "iPhone9,1", "iPhone9,3": return "iPhone 7"
|
|
||||||
case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus"
|
|
||||||
case "iPhone10,1", "iPhone10,4": return "iPhone 8"
|
|
||||||
case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus"
|
|
||||||
case "iPhone10,3", "iPhone10,6": return "iPhone X"
|
|
||||||
case "iPhone11,2": return "iPhone XS"
|
|
||||||
case "iPhone11,4", "iPhone11,6": return "iPhone XS Max"
|
|
||||||
case "iPhone11,8": return "iPhone XR"
|
|
||||||
case "iPhone12,1": return "iPhone 11"
|
|
||||||
case "iPhone12,3": return "iPhone 11 Pro"
|
|
||||||
case "iPhone12,5": return "iPhone 11 Pro Max"
|
|
||||||
case "iPhone13,1": return "iPhone 12 mini"
|
|
||||||
case "iPhone13,2": return "iPhone 12"
|
|
||||||
case "iPhone13,3": return "iPhone 12 Pro"
|
|
||||||
case "iPhone13,4": return "iPhone 12 Pro Max"
|
|
||||||
case "iPhone14,4": return "iPhone 13 mini"
|
|
||||||
case "iPhone14,5": return "iPhone 13"
|
|
||||||
case "iPhone14,2": return "iPhone 13 Pro"
|
|
||||||
case "iPhone14,3": return "iPhone 13 Pro Max"
|
|
||||||
case "iPhone14,7": return "iPhone 14"
|
|
||||||
case "iPhone14,8": return "iPhone 14 Plus"
|
|
||||||
case "iPhone15,2": return "iPhone 14 Pro"
|
|
||||||
case "iPhone15,3": return "iPhone 14 Pro Max"
|
|
||||||
case "iPhone15,4": return "iPhone 15"
|
|
||||||
case "iPhone15,5": return "iPhone 15 Plus"
|
|
||||||
case "iPhone16,1": return "iPhone 15 Pro"
|
|
||||||
case "iPhone16,2": return "iPhone 15 Pro Max"
|
|
||||||
case "iPhone17,3": return "iPhone 16"
|
|
||||||
case "iPhone17,4": return "iPhone 16 Plus"
|
|
||||||
case "iPhone17,1": return "iPhone 16 Pro"
|
|
||||||
case "iPhone17,2": return "iPhone 16 Pro Max"
|
|
||||||
case "iPhone17,5": return "iPhone 16e"
|
|
||||||
case "iPhone8,4": return "iPhone SE"
|
|
||||||
case "iPhone12,8": return "iPhone SE (2nd generation)"
|
|
||||||
case "iPhone14,6": return "iPhone SE (3rd generation)"
|
|
||||||
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return "iPad 2"
|
|
||||||
case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)"
|
|
||||||
case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)"
|
|
||||||
case "iPad6,11", "iPad6,12": return "iPad (5th generation)"
|
|
||||||
case "iPad7,5", "iPad7,6": return "iPad (6th generation)"
|
|
||||||
case "iPad7,11", "iPad7,12": return "iPad (7th generation)"
|
|
||||||
case "iPad11,6", "iPad11,7": return "iPad (8th generation)"
|
|
||||||
case "iPad12,1", "iPad12,2": return "iPad (9th generation)"
|
|
||||||
case "iPad13,18", "iPad13,19": return "iPad (10th generation)"
|
|
||||||
case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air"
|
|
||||||
case "iPad5,3", "iPad5,4": return "iPad Air 2"
|
|
||||||
case "iPad11,3", "iPad11,4": return "iPad Air (3rd generation)"
|
|
||||||
case "iPad13,1", "iPad13,2": return "iPad Air (4th generation)"
|
|
||||||
case "iPad13,16", "iPad13,17": return "iPad Air (5th generation)"
|
|
||||||
case "iPad14,8", "iPad14,9": return "iPad Air (11-inch) (M2)"
|
|
||||||
case "iPad14,10", "iPad14,11": return "iPad Air (13-inch) (M2)"
|
|
||||||
case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad mini"
|
|
||||||
case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad mini 2"
|
|
||||||
case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad mini 3"
|
|
||||||
case "iPad5,1", "iPad5,2": return "iPad mini 4"
|
|
||||||
case "iPad11,1", "iPad11,2": return "iPad mini (5th generation)"
|
|
||||||
case "iPad14,1", "iPad14,2": return "iPad mini (6th generation)"
|
|
||||||
case "iPad16,1", "iPad16,2": return "iPad mini (A17 Pro)"
|
|
||||||
case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)"
|
|
||||||
case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)"
|
|
||||||
case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4": return "iPad Pro (11-inch) (1st generation)"
|
|
||||||
case "iPad8,9", "iPad8,10": return "iPad Pro (11-inch) (2nd generation)"
|
|
||||||
case "iPad13,4", "iPad13,5", "iPad13,6", "iPad13,7": return "iPad Pro (11-inch) (3rd generation)"
|
|
||||||
case "iPad14,3", "iPad14,4": return "iPad Pro (11-inch) (4th generation)"
|
|
||||||
case "iPad16,3", "iPad16,4": return "iPad Pro (11-inch) (M4)"
|
|
||||||
case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch) (1st generation)"
|
|
||||||
case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)"
|
|
||||||
case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8": return "iPad Pro (12.9-inch) (3rd generation)"
|
|
||||||
case "iPad8,11", "iPad8,12": return "iPad Pro (12.9-inch) (4th generation)"
|
|
||||||
case "iPad13,8", "iPad13,9", "iPad13,10", "iPad13,11":return "iPad Pro (12.9-inch) (5th generation)"
|
|
||||||
case "iPad14,5", "iPad14,6": return "iPad Pro (12.9-inch) (6th generation)"
|
|
||||||
case "iPad16,5", "iPad16,6": return "iPad Pro (13-inch) (M4)"
|
|
||||||
case "AppleTV5,3": return "Apple TV"
|
|
||||||
case "AppleTV6,2": return "Apple TV 4K"
|
|
||||||
case "AudioAccessory1,1": return "HomePod"
|
|
||||||
case "AudioAccessory5,1": return "HomePod mini"
|
|
||||||
case "i386", "x86_64", "arm64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))"
|
|
||||||
default: return identifier
|
|
||||||
}
|
|
||||||
#elseif os(tvOS)
|
|
||||||
switch identifier {
|
|
||||||
case "AppleTV5,3": return "Apple TV 4"
|
|
||||||
case "AppleTV6,2", "AppleTV11,1", "AppleTV14,1": return "Apple TV 4K"
|
|
||||||
case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))"
|
|
||||||
default: return identifier
|
|
||||||
}
|
|
||||||
#elseif os(visionOS)
|
|
||||||
switch identifier {
|
|
||||||
case "RealityDevice14,1": return "Apple Vision Pro"
|
|
||||||
default: return identifier
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapToDevice(identifier: identifier)
|
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -35,7 +35,7 @@ struct LaunchGameIntentDef: AppIntent {
|
|||||||
let name = findClosestGameName(input: gameName, games: ryujinx.compactMap(\.titleName))
|
let name = findClosestGameName(input: gameName, games: ryujinx.compactMap(\.titleName))
|
||||||
|
|
||||||
let urlString = "melonx://game?name=\(name ?? gameName)"
|
let urlString = "melonx://game?name=\(name ?? gameName)"
|
||||||
// print(urlString)
|
print(urlString)
|
||||||
if let url = URL(string: urlString) {
|
if let url = URL(string: urlString) {
|
||||||
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ public struct Game: Identifiable, Equatable, Hashable {
|
|||||||
|
|
||||||
gameTemp.icon = UIImage(data: imageData)
|
gameTemp.icon = UIImage(data: imageData)
|
||||||
} else {
|
} else {
|
||||||
// print("Invalid image size.")
|
print("Invalid image size.")
|
||||||
}
|
}
|
||||||
return gameTemp
|
return gameTemp
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ public struct Game: Identifiable, Equatable, Hashable {
|
|||||||
|
|
||||||
let imageSize = Int(gameInfoValue.ImageSize)
|
let imageSize = Int(gameInfoValue.ImageSize)
|
||||||
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
||||||
// print("Invalid image size.")
|
print("Invalid image size.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import GameController
|
|||||||
import Darwin
|
import Darwin
|
||||||
import UIKit
|
import UIKit
|
||||||
import MetalKit
|
import MetalKit
|
||||||
import CoreLocation
|
|
||||||
|
|
||||||
struct MoltenVKSettings: Codable, Hashable {
|
struct MoltenVKSettings: Codable, Hashable {
|
||||||
let string: String
|
let string: String
|
||||||
@ -38,7 +37,6 @@ struct ContentView: View {
|
|||||||
|
|
||||||
// JIT
|
// JIT
|
||||||
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
|
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
|
||||||
@AppStorage("stikJIT") var stikJIT: Bool = false
|
|
||||||
|
|
||||||
// Other Configuration
|
// Other Configuration
|
||||||
@State var isMK8: Bool = false
|
@State var isMK8: Bool = false
|
||||||
@ -81,7 +79,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
_settings = State(initialValue: defaultSettings)
|
_settings = State(initialValue: defaultSettings)
|
||||||
|
|
||||||
// print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue)
|
print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue)
|
||||||
|
|
||||||
initializeSDL()
|
initializeSDL()
|
||||||
}
|
}
|
||||||
@ -121,7 +119,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
private var jitErrorView: some View {
|
private var jitErrorView: some View {
|
||||||
Text("")
|
Text("")
|
||||||
.fullScreenCover(isPresented:Binding(
|
.sheet(isPresented:Binding(
|
||||||
get: { !ryujinx.jitenabled },
|
get: { !ryujinx.jitenabled },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
ryujinx.jitenabled = newValue
|
ryujinx.jitenabled = newValue
|
||||||
@ -132,7 +130,7 @@ struct ContentView: View {
|
|||||||
JITPopover() {
|
JITPopover() {
|
||||||
ryujinx.jitenabled = false
|
ryujinx.jitenabled = false
|
||||||
}
|
}
|
||||||
// .interactiveDismissDisabled()
|
.interactiveDismissDisabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,12 +153,21 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// print(MTLHud.shared.isEnabled)
|
print(MTLHud.shared.isEnabled)
|
||||||
|
|
||||||
initControllerObservers()
|
initControllerObservers()
|
||||||
|
|
||||||
Air.play(AnyView(
|
Air.play(AnyView(
|
||||||
ControllerListView(game: $game)
|
VStack {
|
||||||
|
Image(systemName: "gamecontroller")
|
||||||
|
.font(.system(size: 300))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
Text("Select Game")
|
||||||
|
.font(.system(size: 150))
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
checkJitStatus()
|
checkJitStatus()
|
||||||
@ -281,7 +288,7 @@ struct ContentView: View {
|
|||||||
queue: .main
|
queue: .main
|
||||||
) { notification in
|
) { 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)
|
nativeControllers[controller] = .init(controller)
|
||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
}
|
}
|
||||||
@ -293,7 +300,7 @@ struct ContentView: View {
|
|||||||
queue: .main
|
queue: .main
|
||||||
) { notification in
|
) { 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]?.cleanup()
|
||||||
nativeControllers[controller] = nil
|
nativeControllers[controller] = nil
|
||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
@ -302,8 +309,6 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupEmulation() {
|
private func setupEmulation() {
|
||||||
refreshControllersList()
|
|
||||||
|
|
||||||
isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -321,34 +326,14 @@ struct ContentView: View {
|
|||||||
controllersList.removeAll(where: { $0.id == "0" || (!$0.name.starts(with: "GC - ") && $0 != onscreencontroller) })
|
controllersList.removeAll(where: { $0.id == "0" || (!$0.name.starts(with: "GC - ") && $0 != onscreencontroller) })
|
||||||
controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") }
|
controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") }
|
||||||
|
|
||||||
|
currentControllers = []
|
||||||
|
|
||||||
if !currentControllers.isEmpty, !(currentControllers.count == 1) {
|
if controllersList.count == 1 {
|
||||||
var currentController: [Controller] = []
|
currentControllers.append(controllersList[0])
|
||||||
|
} else if (controllersList.count - 1) >= 1 {
|
||||||
if currentController.count == 1 {
|
for controller in controllersList {
|
||||||
currentController.append(controllersList[0])
|
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
||||||
} else if (controllersList.count - 1) >= 1 {
|
currentControllers.append(controller)
|
||||||
for controller in controllersList {
|
|
||||||
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
|
||||||
currentController.append(controller)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentController == currentControllers {
|
|
||||||
currentControllers = []
|
|
||||||
currentControllers = currentController
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentControllers = []
|
|
||||||
|
|
||||||
if controllersList.count == 1 {
|
|
||||||
currentControllers.append(controllersList[0])
|
|
||||||
} else if (controllersList.count - 1) >= 1 {
|
|
||||||
for controller in controllersList {
|
|
||||||
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
|
||||||
currentControllers.append(controller)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,7 +354,7 @@ struct ContentView: View {
|
|||||||
do {
|
do {
|
||||||
try ryujinx.start(with: config)
|
try ryujinx.start(with: config)
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error: \(error.localizedDescription)")
|
print("Error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +365,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if syncqsubmits {
|
if syncqsubmits {
|
||||||
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "1", 1)
|
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "2", 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,18 +377,13 @@ struct ContentView: View {
|
|||||||
|
|
||||||
private func checkJitStatus() {
|
private func checkJitStatus() {
|
||||||
ryujinx.ryuIsJITEnabled()
|
ryujinx.ryuIsJITEnabled()
|
||||||
if jitStreamerEB {
|
|
||||||
jitStreamerEB = false // byee jitstreamer eb
|
|
||||||
}
|
|
||||||
if !ryujinx.jitenabled {
|
if !ryujinx.jitenabled {
|
||||||
if useTrollStore {
|
if useTrollStore {
|
||||||
askForJIT()
|
askForJIT()
|
||||||
} else if stikJIT {
|
|
||||||
enableJITStik()
|
|
||||||
} else if jitStreamerEB {
|
} else if jitStreamerEB {
|
||||||
enableJITEB()
|
enableJITEB()
|
||||||
} else {
|
} else {
|
||||||
// print("no JIT")
|
print("no JIT")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,12 +391,10 @@ struct ContentView: View {
|
|||||||
private func handleDeepLink(_ url: URL) {
|
private func handleDeepLink(_ url: URL) {
|
||||||
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||||
components.host == "game" {
|
components.host == "game" {
|
||||||
DispatchQueue.main.async {
|
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
||||||
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
game = ryujinx.games.first(where: { $0.titleId == text })
|
||||||
game = ryujinx.games.first(where: { $0.titleId == text })
|
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
||||||
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
game = ryujinx.games.first(where: { $0.titleName == text })
|
||||||
game = ryujinx.games.first(where: { $0.titleName == text })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,136 +407,3 @@ extension Array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocationManager: NSObject, CLLocationManagerDelegate {
|
|
||||||
|
|
||||||
private var locationManager: CLLocationManager
|
|
||||||
|
|
||||||
static let sharedInstance = LocationManager()
|
|
||||||
|
|
||||||
private override init() {
|
|
||||||
locationManager = CLLocationManager()
|
|
||||||
super.init()
|
|
||||||
locationManager.delegate = self
|
|
||||||
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
|
||||||
locationManager.pausesLocationUpdatesAutomatically = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
||||||
// print("wow")
|
|
||||||
}
|
|
||||||
|
|
||||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
||||||
print("Location manager failed with: \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
||||||
if manager.authorizationStatus == .denied {
|
|
||||||
print("Location services are disabled in settings.")
|
|
||||||
} else {
|
|
||||||
startUpdatingLocation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() {
|
|
||||||
if UserDefaults.standard.bool(forKey: "location-enabled") {
|
|
||||||
locationManager.stopUpdatingLocation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startUpdatingLocation() {
|
|
||||||
if UserDefaults.standard.bool(forKey: "location-enabled") {
|
|
||||||
locationManager.requestAlwaysAuthorization()
|
|
||||||
locationManager.allowsBackgroundLocationUpdates = true
|
|
||||||
locationManager.startUpdatingLocation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ControllerListView: View {
|
|
||||||
@State private var selectedIndex = 0
|
|
||||||
@Binding var game: Game?
|
|
||||||
@ObservedObject private var ryujinx = Ryujinx.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List(ryujinx.games.indices, id: \.self) { index in
|
|
||||||
let game = ryujinx.games[index]
|
|
||||||
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
// Game Icon
|
|
||||||
Group {
|
|
||||||
if let icon = game.icon {
|
|
||||||
Image(uiImage: icon)
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(contentMode: .fill)
|
|
||||||
} else {
|
|
||||||
ZStack {
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
Image(systemName: "gamecontroller.fill")
|
|
||||||
.font(.system(size: 24))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: 55, height: 55)
|
|
||||||
.cornerRadius(10)
|
|
||||||
|
|
||||||
// Game Info
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text(game.titleName)
|
|
||||||
.font(.system(size: 16, weight: .medium))
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
|
|
||||||
HStack(spacing: 4) {
|
|
||||||
Text(game.developer)
|
|
||||||
|
|
||||||
if !game.version.isEmpty && game.version != "0" {
|
|
||||||
Text("•")
|
|
||||||
Text("v\(game.version)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.system(size: 14))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.background(selectedIndex == index ? Color.blue.opacity(0.3) : .clear)
|
|
||||||
}
|
|
||||||
.onAppear(perform: setupControllerObservers)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupControllerObservers() {
|
|
||||||
let dpadHandler: GCControllerDirectionPadValueChangedHandler = { _, _, yValue in
|
|
||||||
if yValue == 1.0 {
|
|
||||||
selectedIndex = max(0, selectedIndex - 1)
|
|
||||||
} else if yValue == -1.0 {
|
|
||||||
selectedIndex = min(ryujinx.games.count - 1, selectedIndex + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for controller in GCController.controllers() {
|
|
||||||
print("Controller connected: \(controller.vendorName ?? "Unknown")")
|
|
||||||
controller.playerIndex = .index1
|
|
||||||
|
|
||||||
controller.microGamepad?.dpad.valueChangedHandler = dpadHandler
|
|
||||||
controller.extendedGamepad?.dpad.valueChangedHandler = dpadHandler
|
|
||||||
|
|
||||||
controller.extendedGamepad?.buttonA.pressedChangedHandler = { _, _, pressed in
|
|
||||||
if pressed {
|
|
||||||
print("A button pressed")
|
|
||||||
game = ryujinx.games[selectedIndex]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(
|
|
||||||
forName: .GCControllerDidConnect,
|
|
||||||
object: nil,
|
|
||||||
queue: .main
|
|
||||||
) { _ in
|
|
||||||
setupControllerObservers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GameController
|
import GameController
|
||||||
|
import SwiftUIJoystick
|
||||||
import CoreMotion
|
import CoreMotion
|
||||||
|
|
||||||
struct ControllerView: View {
|
struct ControllerView: View {
|
||||||
@ -14,8 +15,6 @@ struct ControllerView: View {
|
|||||||
@AppStorage("On-ScreenControllerScale") private var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") private var controllerScale: Double = 1.0
|
||||||
@AppStorage("stick-button") private var stickButton = false
|
@AppStorage("stick-button") private var stickButton = false
|
||||||
@State private var isPortrait = true
|
@State private var isPortrait = true
|
||||||
@State var hideDpad = false
|
|
||||||
@State var hideABXY = false
|
|
||||||
@Environment(\.verticalSizeClass) var verticalSizeClass
|
@Environment(\.verticalSizeClass) var verticalSizeClass
|
||||||
|
|
||||||
|
|
||||||
@ -46,22 +45,16 @@ struct ControllerView: View {
|
|||||||
VStack(spacing: 15) {
|
VStack(spacing: 15) {
|
||||||
ShoulderButtonsViewLeft()
|
ShoulderButtonsViewLeft()
|
||||||
ZStack {
|
ZStack {
|
||||||
JoystickController(showBackground: $hideDpad)
|
Joystick()
|
||||||
if !hideDpad {
|
DPadView()
|
||||||
DPadView()
|
|
||||||
.animation(.easeInOut(duration: 0.2), value: hideDpad)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(spacing: 15) {
|
VStack(spacing: 15) {
|
||||||
ShoulderButtonsViewRight()
|
ShoulderButtonsViewRight()
|
||||||
ZStack {
|
ZStack {
|
||||||
JoystickController(iscool: true, showBackground: $hideABXY)
|
Joystick(iscool: true)
|
||||||
if !hideABXY {
|
ABXYView()
|
||||||
ABXYView()
|
|
||||||
.animation(.easeInOut(duration: 0.2), value: hideABXY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,14 +81,11 @@ struct ControllerView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 15) {
|
||||||
ShoulderButtonsViewLeft()
|
ShoulderButtonsViewLeft()
|
||||||
ZStack {
|
ZStack {
|
||||||
JoystickController(showBackground: $hideDpad)
|
Joystick()
|
||||||
if !hideDpad {
|
DPadView()
|
||||||
DPadView()
|
|
||||||
.animation(.easeInOut(duration: 0.2), value: hideDpad)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,14 +95,11 @@ struct ControllerView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 15) {
|
||||||
ShoulderButtonsViewRight()
|
ShoulderButtonsViewRight()
|
||||||
ZStack {
|
ZStack {
|
||||||
JoystickController(iscool: true, showBackground: $hideABXY)
|
Joystick(iscool: true)
|
||||||
if !hideABXY {
|
ABXYView()
|
||||||
ABXYView()
|
|
||||||
.animation(.easeInOut(duration: 0.2), value: hideABXY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,11 +199,11 @@ struct DPadView: View {
|
|||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 7) {
|
VStack(spacing: 5) {
|
||||||
ButtonView(button: .dPadUp)
|
ButtonView(button: .dPadUp)
|
||||||
HStack(spacing: 22) {
|
HStack(spacing: 20) {
|
||||||
ButtonView(button: .dPadLeft)
|
ButtonView(button: .dPadLeft)
|
||||||
Spacer(minLength: 22)
|
Spacer(minLength: 20)
|
||||||
ButtonView(button: .dPadRight)
|
ButtonView(button: .dPadRight)
|
||||||
}
|
}
|
||||||
ButtonView(button: .dPadDown)
|
ButtonView(button: .dPadDown)
|
||||||
@ -237,11 +224,11 @@ struct ABXYView: View {
|
|||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 7) {
|
VStack(spacing: 5) {
|
||||||
ButtonView(button: .X)
|
ButtonView(button: .X)
|
||||||
HStack(spacing: 22) {
|
HStack(spacing: 20) {
|
||||||
ButtonView(button: .Y)
|
ButtonView(button: .Y)
|
||||||
Spacer(minLength: 22)
|
Spacer(minLength: 20)
|
||||||
ButtonView(button: .A)
|
ButtonView(button: .A)
|
||||||
}
|
}
|
||||||
ButtonView(button: .B)
|
ButtonView(button: .B)
|
||||||
@ -272,25 +259,19 @@ struct ButtonView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width, height: height)
|
.frame(width: width, height: height)
|
||||||
.foregroundColor(true ? Color.white.opacity(0.5) : Color.black.opacity(0.5))
|
.foregroundColor(true ? Color.white.opacity(0.9) : Color.black.opacity(0.9))
|
||||||
.background(
|
.background(
|
||||||
Group {
|
Group {
|
||||||
if !button.isTrigger && button != .leftStick && button != .rightStick {
|
if !button.isTrigger {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.fill(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: width * 1.25, height: height * 1.25)
|
||||||
} else if button == .leftStick || button == .rightStick {
|
} else {
|
||||||
Image(systemName: buttonText)
|
Image(systemName: buttonText)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: width * 1.25, height: height * 1.25)
|
||||||
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
||||||
} else if button.isTrigger {
|
|
||||||
Image(systemName: "" + String(turntobutton(buttonText)))
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
|
||||||
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -309,19 +290,6 @@ struct ButtonView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func turntobutton(_ string: String) -> String {
|
|
||||||
var sting = string
|
|
||||||
if string.hasPrefix("zl") || string.hasPrefix("zr") {
|
|
||||||
sting = String(string.dropFirst(3))
|
|
||||||
} else {
|
|
||||||
sting = String(string.dropFirst(2))
|
|
||||||
}
|
|
||||||
sting = sting.replacingOccurrences(of: "rectangle", with: "button")
|
|
||||||
sting = sting.replacingOccurrences(of: ".fill", with: ".horizontal.fill")
|
|
||||||
|
|
||||||
return sting
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleButtonPress() {
|
private func handleButtonPress() {
|
||||||
if !isPressed {
|
if !isPressed {
|
||||||
isPressed = true
|
isPressed = true
|
||||||
|
@ -15,6 +15,7 @@ class Haptics {
|
|||||||
private init() { }
|
private init() { }
|
||||||
|
|
||||||
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||||
|
print("haptics")
|
||||||
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
//
|
|
||||||
// Joystick.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 21/03/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct Joystick: View {
|
|
||||||
@Binding var position: CGPoint
|
|
||||||
@State var joystickSize: CGFloat
|
|
||||||
var boundarySize: CGFloat
|
|
||||||
|
|
||||||
@State private var offset: CGSize = .zero
|
|
||||||
@Binding var showBackground: Bool
|
|
||||||
|
|
||||||
let sensitivity: CGFloat = 1.5
|
|
||||||
|
|
||||||
var dragGesture: some Gesture {
|
|
||||||
DragGesture()
|
|
||||||
.onChanged { value in
|
|
||||||
withAnimation(.easeIn) {
|
|
||||||
showBackground = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let translation = value.translation
|
|
||||||
let distance = sqrt(translation.width * translation.width + translation.height * translation.height)
|
|
||||||
let maxRadius = (boundarySize - joystickSize) / 2
|
|
||||||
let extendedRadius = maxRadius + (joystickSize / 2)
|
|
||||||
|
|
||||||
if distance <= extendedRadius {
|
|
||||||
offset = translation
|
|
||||||
} else {
|
|
||||||
let angle = atan2(translation.height, translation.width)
|
|
||||||
offset = CGSize(width: cos(angle) * extendedRadius, height: sin(angle) * extendedRadius)
|
|
||||||
}
|
|
||||||
|
|
||||||
position = CGPoint(
|
|
||||||
x: max(-1, min(1, (offset.width / extendedRadius) * sensitivity)),
|
|
||||||
y: max(-1, min(1, (offset.height / extendedRadius) * sensitivity))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
offset = .zero
|
|
||||||
position = .zero
|
|
||||||
withAnimation(.easeOut) {
|
|
||||||
showBackground = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
Circle()
|
|
||||||
.fill(Color.clear.opacity(0))
|
|
||||||
.frame(width: boundarySize, height: boundarySize)
|
|
||||||
|
|
||||||
if showBackground {
|
|
||||||
Circle()
|
|
||||||
.fill(Color.gray.opacity(0.4))
|
|
||||||
.frame(width: boundarySize, height: boundarySize)
|
|
||||||
.animation(.easeInOut(duration: 0.1), value: showBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
Circle()
|
|
||||||
.fill(Color.white.opacity(0.5))
|
|
||||||
.frame(width: joystickSize, height: joystickSize)
|
|
||||||
.background(
|
|
||||||
Circle()
|
|
||||||
.fill(Color.gray.opacity(0.3))
|
|
||||||
.frame(width: joystickSize * 1.25, height: joystickSize * 1.25)
|
|
||||||
)
|
|
||||||
.offset(offset)
|
|
||||||
.gesture(dragGesture)
|
|
||||||
}
|
|
||||||
.frame(width: boundarySize, height: boundarySize)
|
|
||||||
.onChange(of: showBackground) { newValue in
|
|
||||||
if newValue {
|
|
||||||
joystickSize *= 1.4
|
|
||||||
} else {
|
|
||||||
joystickSize = (boundarySize * 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,13 +7,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftUIJoystick
|
||||||
|
|
||||||
struct JoystickController: View {
|
public struct Joystick: View {
|
||||||
@State var iscool: Bool? = nil
|
@State var iscool: Bool? = nil
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Binding var showBackground: Bool
|
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
@State var position: CGPoint = CGPoint(x: 0, y: 0)
|
|
||||||
var dragDiameter: CGFloat {
|
var dragDiameter: CGFloat {
|
||||||
var selfs = CGFloat(160)
|
var selfs = CGFloat(160)
|
||||||
selfs *= controllerScale
|
selfs *= controllerScale
|
||||||
@ -23,21 +23,39 @@ struct JoystickController: View {
|
|||||||
|
|
||||||
return selfs
|
return selfs
|
||||||
}
|
}
|
||||||
|
private let shape: JoystickShape = .circle
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
VStack {
|
VStack{
|
||||||
Joystick(position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground)
|
JoystickBuilder(
|
||||||
.onChange(of: position) { newValue in
|
monitor: self.joystickMonitor,
|
||||||
let scaledX = Float(newValue.x)
|
width: self.dragDiameter,
|
||||||
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
shape: .circle,
|
||||||
// print("Joystick Position: (\(scaledX), \(scaledY))")
|
background: {
|
||||||
|
Text("")
|
||||||
|
.hidden()
|
||||||
|
},
|
||||||
|
foreground: {
|
||||||
|
Circle()
|
||||||
|
.fill(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.7))
|
||||||
|
.background(
|
||||||
|
Circle()
|
||||||
|
.fill(colorScheme == .dark ? Color.gray.opacity(0.3) : Color.gray.opacity(0.2))
|
||||||
|
.frame(width: (dragDiameter / 4) * 1.2, height: (dragDiameter / 4) * 1.2)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
locksInPlace: false)
|
||||||
|
.onChange(of: self.joystickMonitor.xyPoint) { newValue in
|
||||||
|
let scaledX = Float(newValue.x)
|
||||||
|
let scaledY = Float(newValue.y) // my dumbass broke this by having -y instead of y :/
|
||||||
|
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||||
|
|
||||||
if iscool != nil {
|
if iscool != nil {
|
||||||
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
||||||
} else {
|
} else {
|
||||||
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
|
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: newValue.x, y: newValue.y)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ public class Air {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func didConnect(sender: NSNotification) {
|
@objc func didConnect(sender: NSNotification) {
|
||||||
// print("AirKit - Connect")
|
print("AirKit - Connect")
|
||||||
self.connected = true
|
self.connected = true
|
||||||
guard let screen: UIScreen = sender.object as? UIScreen else { return }
|
guard let screen: UIScreen = sender.object as? UIScreen else { return }
|
||||||
add(screen: screen) { success in
|
add(screen: screen) { success in
|
||||||
@ -69,35 +69,35 @@ public class Air {
|
|||||||
|
|
||||||
func add(screen: UIScreen, completion: @escaping (Bool) -> ()) {
|
func add(screen: UIScreen, completion: @escaping (Bool) -> ()) {
|
||||||
|
|
||||||
// print("AirKit - Add Screen")
|
print("AirKit - Add Screen")
|
||||||
|
|
||||||
airScreen = screen
|
airScreen = screen
|
||||||
|
|
||||||
airWindow = UIWindow(frame: airScreen!.bounds)
|
airWindow = UIWindow(frame: airScreen!.bounds)
|
||||||
|
|
||||||
guard let viewController: UIViewController = hostingController else {
|
guard let viewController: UIViewController = hostingController else {
|
||||||
// print("AirKit - Add - Failed: Hosting Controller Not Found")
|
print("AirKit - Add - Failed: Hosting Controller Not Found")
|
||||||
completion(false)
|
completion(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
findWindowScene(for: airScreen!) { windowScene in
|
findWindowScene(for: airScreen!) { windowScene in
|
||||||
guard let airWindowScene: UIWindowScene = windowScene else {
|
guard let airWindowScene: UIWindowScene = windowScene else {
|
||||||
// print("AirKit - Add - Failed: Window Scene Not Found")
|
print("AirKit - Add - Failed: Window Scene Not Found")
|
||||||
completion(false)
|
completion(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.airWindow?.rootViewController = viewController
|
self.airWindow?.rootViewController = viewController
|
||||||
self.airWindow?.windowScene = airWindowScene
|
self.airWindow?.windowScene = airWindowScene
|
||||||
self.airWindow?.isHidden = false
|
self.airWindow?.isHidden = false
|
||||||
// print("AirKit - Add Screen - Done")
|
print("AirKit - Add Screen - Done")
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) {
|
func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) {
|
||||||
// print("AirKit - Find Window Scene")
|
print("AirKit - Find Window Scene")
|
||||||
var matchingWindowScene: UIWindowScene? = nil
|
var matchingWindowScene: UIWindowScene? = nil
|
||||||
let scenes = UIApplication.shared.connectedScenes
|
let scenes = UIApplication.shared.connectedScenes
|
||||||
for scene in scenes {
|
for scene in scenes {
|
||||||
@ -120,23 +120,23 @@ public class Air {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func didDisconnect() {
|
@objc func didDisconnect() {
|
||||||
// print("AirKit - Disconnect")
|
print("AirKit - Disconnect")
|
||||||
remove()
|
remove()
|
||||||
connected = false
|
connected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove() {
|
func remove() {
|
||||||
// print("AirKit - Remove")
|
print("AirKit - Remove")
|
||||||
airWindow = nil
|
airWindow = nil
|
||||||
airScreen = nil
|
airScreen = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didBecomeActive() {
|
@objc func didBecomeActive() {
|
||||||
// print("AirKit - App Active")
|
print("AirKit - App Active")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func willResignActive() {
|
@objc func willResignActive() {
|
||||||
// print("AirKit - App Inactive")
|
print("AirKit - App Inactive")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import SwiftUI
|
|||||||
public extension View {
|
public extension View {
|
||||||
|
|
||||||
func airPlay() -> some View {
|
func airPlay() -> some View {
|
||||||
// print("AirKit - airPlay")
|
print("AirKit - airPlay")
|
||||||
Air.play(AnyView(self))
|
Air.play(AnyView(self))
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,6 @@ struct EmulationView: View {
|
|||||||
@Binding var startgame: Game?
|
@Binding var startgame: Game?
|
||||||
|
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) var scenePhase
|
||||||
@State private var isInBackground = false
|
|
||||||
@AppStorage("location-enabled") var locationenabled: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if isAirplaying {
|
if isAirplaying {
|
||||||
@ -29,7 +26,7 @@ struct EmulationView: View {
|
|||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Air.play(AnyView(MetalView().ignoresSafeArea().edgesIgnoringSafeArea(.all)))
|
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MetalView() // The Emulation View
|
MetalView() // The Emulation View
|
||||||
@ -91,26 +88,12 @@ struct EmulationView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
LocationManager.sharedInstance.startUpdatingLocation()
|
|
||||||
Air.shared.connectionCallbacks.append { cool in
|
Air.shared.connectionCallbacks.append { cool in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
isAirplaying = cool
|
isAirplaying = cool
|
||||||
// print(cool)
|
print(cool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: scenePhase) { newPhase in
|
|
||||||
// Detect when the app enters the background
|
|
||||||
if newPhase == .background {
|
|
||||||
stop_emulation(true)
|
|
||||||
isInBackground = true
|
|
||||||
} else if newPhase == .active {
|
|
||||||
stop_emulation(false)
|
|
||||||
isInBackground = false
|
|
||||||
} else if newPhase == .inactive {
|
|
||||||
stop_emulation(true)
|
|
||||||
isInBackground = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ class MeloMTKView: MTKView {
|
|||||||
let index = activeTouches.firstIndex(of: touch)!
|
let index = activeTouches.firstIndex(of: touch)!
|
||||||
|
|
||||||
let scaledLocation = scaleToTargetResolution(location)!
|
let scaledLocation = scaleToTargetResolution(location)!
|
||||||
// // print("Touch began at: \(scaledLocation) and \(self.aspectRatio)")
|
print("Touch began at: \(scaledLocation) and \(self.aspectRatio)")
|
||||||
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ class MeloMTKView: MTKView {
|
|||||||
if let index = activeTouches.firstIndex(of: touch) {
|
if let index = activeTouches.firstIndex(of: touch) {
|
||||||
activeTouches.remove(at: index)
|
activeTouches.remove(at: index)
|
||||||
|
|
||||||
// // print("Touch ended for index \(index)")
|
print("Touch ended for index \(index)")
|
||||||
touch_ended(Int32(index))
|
touch_ended(Int32(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,14 +139,14 @@ class MeloMTKView: MTKView {
|
|||||||
guard let scaledLocation = scaleToTargetResolution(location) else {
|
guard let scaledLocation = scaleToTargetResolution(location) else {
|
||||||
if let index = activeTouches.firstIndex(of: touch) {
|
if let index = activeTouches.firstIndex(of: touch) {
|
||||||
activeTouches.remove(at: index)
|
activeTouches.remove(at: index)
|
||||||
// // print("Touch left active area, removed index \(index)")
|
print("Touch left active area, removed index \(index)")
|
||||||
touch_ended(Int32(index))
|
touch_ended(Int32(index))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = activeTouches.firstIndex(of: touch) {
|
if let index = activeTouches.firstIndex(of: touch) {
|
||||||
// // print("Touch moved to: \(scaledLocation)")
|
print("Touch moved to: \(scaledLocation)")
|
||||||
touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
//
|
|
||||||
// GameRequirementsCache.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 21/03/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class GameCompatibiliryCache {
|
|
||||||
static let shared = GameCompatibiliryCache()
|
|
||||||
private let cacheKey = "gameRequirementsCache"
|
|
||||||
private let timestampKey = "gameRequirementsCacheTimestamp"
|
|
||||||
|
|
||||||
private let cacheDuration: TimeInterval = Double.random(in: 3...5) * 24 * 60 * 60 // Randomly pick 3-5 days
|
|
||||||
|
|
||||||
func getCachedData() -> [GameRequirements]? {
|
|
||||||
guard let cachedData = UserDefaults.standard.data(forKey: cacheKey),
|
|
||||||
let timestamp = UserDefaults.standard.object(forKey: timestampKey) as? Date else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeElapsed = Date().timeIntervalSince(timestamp)
|
|
||||||
if timeElapsed > cacheDuration {
|
|
||||||
clearCache()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return try? JSONDecoder().decode([GameRequirements].self, from: cachedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCachedData(_ data: [GameRequirements]) {
|
|
||||||
if let encodedData = try? JSONEncoder().encode(data) {
|
|
||||||
UserDefaults.standard.set(encodedData, forKey: cacheKey)
|
|
||||||
UserDefaults.standard.set(Date(), forKey: timestampKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearCache() {
|
|
||||||
UserDefaults.standard.removeObject(forKey: cacheKey)
|
|
||||||
UserDefaults.standard.removeObject(forKey: timestampKey)
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
|||||||
struct GameInfoSheet: View {
|
struct GameInfoSheet: View {
|
||||||
let game: Game
|
let game: Game
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
iOSNav {
|
iOSNav {
|
||||||
@ -44,7 +44,7 @@ struct GameInfoSheet: View {
|
|||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
Text(game.developer)
|
Text(game.developer)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.padding(.vertical, 3)
|
.padding(.vertical, 3)
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ struct GameInfoSheet: View {
|
|||||||
Text("**Version**")
|
Text("**Version**")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(game.version)
|
Text(game.version)
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundStyle(Color.secondary)
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("**Title ID**")
|
Text("**Title ID**")
|
||||||
@ -69,36 +69,36 @@ struct GameInfoSheet: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(game.titleId)
|
Text(game.titleId)
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundStyle(Color.secondary)
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("**Game Size**")
|
Text("**Game Size**")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
Text("\(fetchFileSize(for: game.fileURL) ?? 0) bytes")
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundStyle(Color.secondary)
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("**File Type**")
|
Text("**File Type**")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(getFileType(game.fileURL))
|
Text(getFileType(game.fileURL))
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundStyle(Color.secondary)
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("**Game URL**")
|
Text("**Game URL**")
|
||||||
Text(trimGameURL(game.fileURL))
|
Text(trimGameURL(game.fileURL))
|
||||||
.foregroundColor(Color.secondary)
|
.foregroundStyle(Color.secondary)
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Information")
|
Text("Information")
|
||||||
}
|
}
|
||||||
// .headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
}
|
}
|
||||||
.navigationTitle(game.titleName)
|
.navigationTitle(game.titleName)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button("Dismiss") {
|
Button("Done") {
|
||||||
presentationMode.wrappedValue.dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ struct GameInfoSheet: View {
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error getting file size: \(error)")
|
print("Error getting file size: \(error)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct JITPopover: View {
|
struct JITPopover: View {
|
||||||
var onJITEnabled: () -> Void
|
var onJITEnabled: () -> Void
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@State var isJIT: Bool = false
|
@State var isJIT: Bool = false
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -35,7 +35,7 @@ struct JITPopover: View {
|
|||||||
|
|
||||||
|
|
||||||
if isJIT {
|
if isJIT {
|
||||||
presentationMode.wrappedValue.dismiss()
|
dismiss()
|
||||||
onJITEnabled()
|
onJITEnabled()
|
||||||
|
|
||||||
Ryujinx.shared.ryuIsJITEnabled()
|
Ryujinx.shared.ryuIsJITEnabled()
|
||||||
|
@ -6,20 +6,25 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
|
||||||
|
|
||||||
struct LogFileView: View {
|
struct LogFileView: View {
|
||||||
@StateObject var logsModel = LogViewModel()
|
@State private var logs: [String] = []
|
||||||
@State private var showingLogs = false
|
@State private var showingLogs = false
|
||||||
|
|
||||||
public var isfps: Bool
|
public var isfps: Bool
|
||||||
|
|
||||||
private let fileManager = FileManager.default
|
private let fileManager = FileManager.default
|
||||||
private let maxDisplayLines = 4
|
private let maxDisplayLines = 10
|
||||||
|
|
||||||
|
private var dateFormatter: DateFormatter {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
ForEach(logsModel.logs.suffix(maxDisplayLines), id: \.self) { log in
|
ForEach(logs.suffix(maxDisplayLines), id: \.self) { log in
|
||||||
Text(log)
|
Text(log)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
@ -29,38 +34,85 @@ struct LogFileView: View {
|
|||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.onAppear {
|
||||||
|
startLogFileWatching()
|
||||||
|
}
|
||||||
|
.onChange(of: logs) { newLogs in
|
||||||
|
print("Logs updated: \(newLogs.count) entries")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getLatestLogFile() -> URL? {
|
||||||
|
let logsDirectory = URL.documentsDirectory.appendingPathComponent("Logs")
|
||||||
|
let currentDate = Date()
|
||||||
|
|
||||||
|
do {
|
||||||
|
try fileManager.createDirectory(at: logsDirectory, withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
let logFiles = try fileManager.contentsOfDirectory(at: logsDirectory, includingPropertiesForKeys: [.creationDateKey])
|
||||||
|
.filter {
|
||||||
|
let filename = $0.lastPathComponent
|
||||||
|
guard filename.hasPrefix("MeloNX_") && filename.hasSuffix(".log") else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateString = filename.replacingOccurrences(of: "MeloNX_", with: "").replacingOccurrences(of: ".log", with: "")
|
||||||
|
guard let logDate = dateFormatter.date(from: dateString) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calendar.current.isDate(logDate, inSameDayAs: currentDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortedLogFiles = logFiles.sorted {
|
||||||
|
$0.lastPathComponent > $1.lastPathComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedLogFiles.first
|
||||||
|
} catch {
|
||||||
|
print("Error finding log files: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func readLatestLogFile() {
|
||||||
|
guard let logFileURL = getLatestLogFile() else {
|
||||||
|
print("no logs?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print(logFileURL)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let logContents = try String(contentsOf: logFileURL)
|
||||||
|
let allLines = logContents.components(separatedBy: .newlines)
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
|
self.logs = Array(allLines)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error reading log file: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startLogFileWatching() {
|
||||||
|
showingLogs = true
|
||||||
|
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
|
||||||
|
if showingLogs {
|
||||||
|
self.readLatestLogFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isfps {
|
||||||
|
sleep(1)
|
||||||
|
if get_current_fps() != 0 {
|
||||||
|
stopLogFileWatching()
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopLogFileWatching() {
|
private func stopLogFileWatching() {
|
||||||
showingLogs = false
|
showingLogs = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LogViewModel: ObservableObject {
|
|
||||||
@Published var logs: [String] = []
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
init() {
|
|
||||||
_ = LogCapture.shared
|
|
||||||
|
|
||||||
NotificationCenter.default.publisher(for: .newLogCaptured)
|
|
||||||
.receive(on: RunLoop.main)
|
|
||||||
.sink { [weak self] _ in
|
|
||||||
self?.updateLogs()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
updateLogs()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateLogs() {
|
|
||||||
logs = LogCapture.shared.capturedLogs
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearLogs() {
|
|
||||||
LogCapture.shared.capturedLogs = []
|
|
||||||
updateLogs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -33,32 +33,18 @@ struct MeloNXUpdateSheet: View {
|
|||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
if #available(iOS 15.0, *) {
|
Button(action: {
|
||||||
Button(action: {
|
if let url = URL(string: updateInfo.download_link) {
|
||||||
if let url = URL(string: updateInfo.download_link) {
|
UIApplication.shared.open(url)
|
||||||
UIApplication.shared.open(url)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Text("Download Now")
|
|
||||||
.font(.title3)
|
|
||||||
.bold()
|
|
||||||
.frame(width: 300, height: 40)
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
}) {
|
||||||
.frame(alignment: .bottom)
|
Text("Download Now")
|
||||||
} else {
|
.font(.title3)
|
||||||
Button(action: {
|
.bold()
|
||||||
if let url = URL(string: updateInfo.download_link) {
|
.frame(width: 300, height: 40)
|
||||||
UIApplication.shared.open(url)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Text("Download Now")
|
|
||||||
.font(.title3)
|
|
||||||
.bold()
|
|
||||||
.frame(width: 300, height: 40)
|
|
||||||
}
|
|
||||||
.frame(alignment: .bottom)
|
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.frame(alignment: .bottom)
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.navigationTitle("Version \(updateInfo.version_number) Available!")
|
.navigationTitle("Version \(updateInfo.version_number) Available!")
|
||||||
|
@ -46,7 +46,7 @@ struct DLCManagerSheet: View {
|
|||||||
@Binding var game: Game!
|
@Binding var game: Game!
|
||||||
@State private var isSelectingGameDLC = false
|
@State private var isSelectingGameDLC = false
|
||||||
@State private var dlcs: [DownloadableContentContainer] = []
|
@State private var dlcs: [DownloadableContentContainer] = []
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -66,7 +66,7 @@ struct DLCManagerSheet: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
Button("Done") {
|
Button("Done") {
|
||||||
presentationMode.wrappedValue.dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,56 +127,27 @@ struct DLCManagerSheet: View {
|
|||||||
|
|
||||||
|
|
||||||
private func dlcRow(_ dlc: DownloadableContentContainer) -> some View {
|
private func dlcRow(_ dlc: DownloadableContentContainer) -> some View {
|
||||||
Group {
|
Button {
|
||||||
if #available(iOS 15.0, *) {
|
toggleDLC(dlc)
|
||||||
Button {
|
} label: {
|
||||||
toggleDLC(dlc)
|
HStack {
|
||||||
} label: {
|
Text(dlc.filename)
|
||||||
HStack {
|
.foregroundStyle(.primary)
|
||||||
Text(dlc.filename)
|
Spacer()
|
||||||
.foregroundColor(.primary)
|
Image(systemName: dlc.isEnabled ? "checkmark.circle.fill" : "circle")
|
||||||
Spacer()
|
.foregroundStyle(dlc.isEnabled ? .primary : .secondary)
|
||||||
Image(systemName: dlc.isEnabled ? "checkmark.circle.fill" : "circle")
|
.imageScale(.large)
|
||||||
.foregroundColor(dlc.isEnabled ? .primary : .secondary)
|
}
|
||||||
.imageScale(.large)
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.buttonStyle(.plain)
|
||||||
}
|
.swipeActions(edge: .trailing) {
|
||||||
.buttonStyle(.plain)
|
Button(role: .destructive) {
|
||||||
.swipeActions(edge: .trailing) {
|
if let index = dlcs.firstIndex(where: { $0.id == dlc.id }) {
|
||||||
Button(role: .destructive) {
|
removeDLC(at: IndexSet(integer: index))
|
||||||
if let index = dlcs.firstIndex(where: { $0.id == dlc.id }) {
|
|
||||||
removeDLC(at: IndexSet(integer: index))
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Label("Delete", systemImage: "trash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button {
|
|
||||||
toggleDLC(dlc)
|
|
||||||
} label: {
|
|
||||||
HStack {
|
|
||||||
Text(dlc.filename)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: dlc.isEnabled ? "checkmark.circle.fill" : "circle")
|
|
||||||
.foregroundColor(dlc.isEnabled ? .primary : .secondary)
|
|
||||||
.imageScale(.large)
|
|
||||||
}
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.contextMenu {
|
|
||||||
Button {
|
|
||||||
if let index = dlcs.firstIndex(where: { $0.id == dlc.id }) {
|
|
||||||
removeDLC(at: IndexSet(integer: index))
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Label("Delete", systemImage: "trash")
|
|
||||||
.foregroundColor(.red)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Delete", systemImage: "trash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,7 +261,7 @@ private extension DLCManagerSheet {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
} catch {
|
} catch {
|
||||||
// print("Error loading DLCs: \(error)")
|
print("Error loading DLCs: \(error)")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,7 +300,7 @@ extension Array where Element: AnyObject {
|
|||||||
|
|
||||||
// MARK: - URL Extension
|
// MARK: - URL Extension
|
||||||
extension URL {
|
extension URL {
|
||||||
@available(iOS, introduced: 14.0, deprecated: 16.0, message: "Use URL.documentsDirectory on iOS 16 and above")
|
@available(iOS, introduced: 15.0, deprecated: 16.0, message: "Use URL.documentsDirectory on iOS 16 and above")
|
||||||
static var documentsDirectory: URL {
|
static var documentsDirectory: URL {
|
||||||
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
return documentDirectory
|
return documentDirectory
|
||||||
|
@ -14,7 +14,7 @@ struct UpdateManagerSheet: View {
|
|||||||
@Binding var game: Game?
|
@Binding var game: Game?
|
||||||
@State private var isSelectingGameUpdate = false
|
@State private var isSelectingGameUpdate = false
|
||||||
@State private var jsonURL: URL? = nil
|
@State private var jsonURL: URL? = nil
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
// MARK: - Models
|
// MARK: - Models
|
||||||
class UpdateItem: Identifiable, ObservableObject {
|
class UpdateItem: Identifiable, ObservableObject {
|
||||||
@ -51,7 +51,7 @@ struct UpdateManagerSheet: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
Button("Done") {
|
Button("Done") {
|
||||||
presentationMode.wrappedValue.dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,26 +106,15 @@ struct UpdateManagerSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateRow(_ update: UpdateItem) -> some View {
|
private func updateRow(_ update: UpdateItem) -> some View {
|
||||||
Group {
|
|
||||||
if #available(iOS 15, *) {
|
|
||||||
updateRowNew(update)
|
|
||||||
} else {
|
|
||||||
updateRowOld(update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@available(iOS 15, *)
|
|
||||||
private func updateRowNew(_ update: UpdateItem) -> some View {
|
|
||||||
Button {
|
Button {
|
||||||
toggleSelection(update)
|
toggleSelection(update)
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(update.filename)
|
Text(update.filename)
|
||||||
.foregroundColor(.primary)
|
.foregroundStyle(.primary)
|
||||||
Spacer()
|
Spacer()
|
||||||
Image(systemName: update.isSelected ? "checkmark.circle.fill" : "circle")
|
Image(systemName: update.isSelected ? "checkmark.circle.fill" : "circle")
|
||||||
.foregroundColor(update.isSelected ? .primary : .secondary)
|
.foregroundStyle(update.isSelected ? .primary : .secondary)
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
@ -142,31 +131,6 @@ struct UpdateManagerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateRowOld(_ update: UpdateItem) -> some View {
|
|
||||||
Button {
|
|
||||||
toggleSelection(update)
|
|
||||||
} label: {
|
|
||||||
HStack {
|
|
||||||
Text(update.filename)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: update.isSelected ? "checkmark.circle.fill" : "circle")
|
|
||||||
.foregroundColor(update.isSelected ? .primary : .secondary)
|
|
||||||
.imageScale(.large)
|
|
||||||
}
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
.contextMenu {
|
|
||||||
Button {
|
|
||||||
if let index = updates.firstIndex(where: { $0.path == update.path }) {
|
|
||||||
removeUpdate(at: IndexSet(integer: index))
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Label("Delete", systemImage: "trash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Functions
|
// MARK: - Functions
|
||||||
private func loadData() {
|
private func loadData() {
|
||||||
guard let game = game else { return }
|
guard let game = game else { return }
|
||||||
@ -282,12 +246,12 @@ struct UpdateManagerSheet: View {
|
|||||||
updates = updates.map { item in
|
updates = updates.map { item in
|
||||||
var mutableItem = item
|
var mutableItem = item
|
||||||
mutableItem.isSelected = item.path == update.path && !update.isSelected
|
mutableItem.isSelected = item.path == update.path && !update.isSelected
|
||||||
// print(mutableItem.isSelected)
|
print(mutableItem.isSelected)
|
||||||
// print(update.isSelected)
|
print(update.isSelected)
|
||||||
return mutableItem
|
return mutableItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// print(updates)
|
print(updates)
|
||||||
|
|
||||||
saveJSON()
|
saveJSON()
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import UniformTypeIdentifiers
|
|
||||||
import AVFoundation
|
|
||||||
|
|
||||||
|
|
||||||
extension UIDocumentPickerViewController {
|
|
||||||
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
|
|
||||||
return fix_init(forOpeningContentTypes: contentTypes, asCopy: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct MeloNXApp: App {
|
struct MeloNXApp: App {
|
||||||
@ -31,17 +24,12 @@ struct MeloNXApp: App {
|
|||||||
@State var finished = false
|
@State var finished = false
|
||||||
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
||||||
|
|
||||||
@AppStorage("location-enabled") var locationenabled: Bool = false
|
|
||||||
@AppStorage("checkForUpdate") var checkForUpdate: Bool = true
|
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
if finishedStorage {
|
if finishedStorage {
|
||||||
ContentView()
|
ContentView()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if checkForUpdate {
|
checkLatestVersion()
|
||||||
checkLatestVersion()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.sheet(isPresented: Binding(
|
.sheet(isPresented: Binding(
|
||||||
get: { showOutOfDateSheet && updateInfo != nil },
|
get: { showOutOfDateSheet && updateInfo != nil },
|
||||||
@ -76,22 +64,22 @@ struct MeloNXApp: App {
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
let urlString = "http://192.168.178.116:8000/api/latest_release"
|
let urlString = "http://192.168.178.116:8000/api/latest_release"
|
||||||
#else
|
#else
|
||||||
let urlString = "https://melonx.net/api/latest_release"
|
let urlString = "https://melonx.org/api/latest_release"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
guard let url = URL(string: urlString) else {
|
guard let url = URL(string: urlString) else {
|
||||||
// print("Invalid URL")
|
print("Invalid URL")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
// print("Error checking for new version: \(error)")
|
print("Error checking for new version: \(error)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let data = data else {
|
guard let data = data else {
|
||||||
// print("No data received")
|
print("No data received")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +94,7 @@ struct MeloNXApp: App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// print("Failed to decode response: \(error)")
|
print("Failed to decode response: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,17 +54,12 @@ struct SetupView: View {
|
|||||||
) { result in
|
) { result in
|
||||||
handleFirmwareImport(result: result)
|
handleFirmwareImport(result: result)
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showAlert) {
|
.alert(alertMessage, isPresented: $showAlert) {
|
||||||
Alert(title: Text(alertMessage), dismissButton: .default(Text("OK")))
|
Button("OK", role: .cancel) {}
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showSkipAlert) {
|
.alert("Skip Setup?", isPresented: $showSkipAlert) {
|
||||||
Alert(
|
Button("Skip", role: .destructive) { finished = true }
|
||||||
title: Text("Skip Setup?"),
|
Button("Cancel", role: .cancel) {}
|
||||||
primaryButton: .destructive(Text("Skip")) {
|
|
||||||
finished = true
|
|
||||||
},
|
|
||||||
secondaryButton: .cancel()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
initialize()
|
initialize()
|
||||||
@ -395,7 +390,7 @@ struct SetupView: View {
|
|||||||
|
|
||||||
let iconFileName = iconFiles.last else {
|
let iconFileName = iconFiles.last else {
|
||||||
|
|
||||||
// print("Could not find icons in bundle")
|
print("Could not find icons in bundle")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 98 KiB |
Binary file not shown.
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>AvailableLibraries</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>BinaryPath</key>
|
||||||
|
<string>MoltenVK.framework/MoltenVK</string>
|
||||||
|
<key>LibraryIdentifier</key>
|
||||||
|
<string>ios-arm64</string>
|
||||||
|
<key>LibraryPath</key>
|
||||||
|
<string>MoltenVK.framework</string>
|
||||||
|
<key>SupportedArchitectures</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>SupportedPlatform</key>
|
||||||
|
<string>ios</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XFWK</string>
|
||||||
|
<key>XCFrameworkFormatVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
Binary file not shown.
Binary file not shown.
@ -38,9 +38,8 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>location</string>
|
|
||||||
<string>processing</string>
|
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
|
<string>processing</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
@ -11,7 +11,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
class NoWxCache : IDisposable
|
class NoWxCache : IDisposable
|
||||||
{
|
{
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int SharedCacheSize = 512 * 1024 * 1024;
|
private const int SharedCacheSize = 2047 * 1024 * 1024;
|
||||||
private const int LocalCacheSize = 128 * 1024 * 1024;
|
private const int LocalCacheSize = 128 * 1024 * 1024;
|
||||||
|
|
||||||
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
||||||
|
@ -609,64 +609,23 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public byte[] GetData(int x, int y, int width, int height)
|
public byte[] GetData(int x, int y, int width, int height)
|
||||||
{
|
{
|
||||||
const int MaxChunkSize = 1024 * 1024 * 96; // 96MB Chunks
|
|
||||||
|
|
||||||
int size = width * height * Info.BytesPerPixel;
|
int size = width * height * Info.BytesPerPixel;
|
||||||
|
using var bufferHolder = _gd.BufferManager.Create(_gd, size);
|
||||||
|
|
||||||
|
using (var cbs = _gd.CommandBufferPool.Rent())
|
||||||
|
{
|
||||||
|
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
||||||
|
var image = GetImage().Get(cbs).Value;
|
||||||
|
|
||||||
|
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferHolder.WaitForFences();
|
||||||
byte[] bitmap = new byte[size];
|
byte[] bitmap = new byte[size];
|
||||||
|
GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span<byte>.Empty).CopyTo(bitmap);
|
||||||
if (size <= MaxChunkSize)
|
|
||||||
{
|
|
||||||
using var bufferHolder = _gd.BufferManager.Create(_gd, size);
|
|
||||||
using (var cbs = _gd.CommandBufferPool.Rent())
|
|
||||||
{
|
|
||||||
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
|
||||||
var image = GetImage().Get(cbs).Value;
|
|
||||||
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferHolder.WaitForFences();
|
|
||||||
GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span<byte>.Empty).CopyTo(bitmap);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int dataPerPixel = Info.BytesPerPixel;
|
|
||||||
int rowStride = width * dataPerPixel;
|
|
||||||
int rowsPerChunk = Math.Max(1, MaxChunkSize / rowStride);
|
|
||||||
int originalHeight = height;
|
|
||||||
int currentY = y;
|
|
||||||
int bitmapOffset = 0;
|
|
||||||
|
|
||||||
while (currentY < y + originalHeight)
|
|
||||||
{
|
|
||||||
int chunkHeight = Math.Min(rowsPerChunk, y + originalHeight - currentY);
|
|
||||||
|
|
||||||
if (chunkHeight <= 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
int chunkSize = chunkHeight * rowStride;
|
|
||||||
|
|
||||||
// Process this chunk
|
|
||||||
using var bufferHolder = _gd.BufferManager.Create(_gd, chunkSize);
|
|
||||||
using (var cbs = _gd.CommandBufferPool.Rent())
|
|
||||||
{
|
|
||||||
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
|
||||||
var image = GetImage().Get(cbs).Value;
|
|
||||||
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, chunkSize, true, 0, 0, x, currentY, width, chunkHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferHolder.WaitForFences();
|
|
||||||
GetDataFromBuffer(bufferHolder.GetDataStorage(0, chunkSize), chunkSize, Span<byte>.Empty)
|
|
||||||
.CopyTo(new Span<byte>(bitmap, bitmapOffset, chunkSize));
|
|
||||||
|
|
||||||
currentY += chunkHeight;
|
|
||||||
bitmapOffset += chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public PinnedSpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
BackgroundResource resources = _gd.BackgroundResources.Get();
|
BackgroundResource resources = _gd.BackgroundResources.Get();
|
||||||
@ -779,28 +738,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return GetDataFromBuffer(result, size, result);
|
return GetDataFromBuffer(result, size, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer = 0, int level = 0)
|
private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level)
|
||||||
{
|
{
|
||||||
const int MaxChunkSize = 1024 * 1024 * 96; // 96MB Chunks
|
|
||||||
|
|
||||||
int size = GetBufferDataLength(Info.GetMipSize(level));
|
int size = GetBufferDataLength(Info.GetMipSize(level));
|
||||||
|
|
||||||
if (size <= MaxChunkSize)
|
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
|
||||||
{
|
return GetDataFromBuffer(result, size, result);
|
||||||
Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
|
|
||||||
return GetDataFromBuffer(result, size, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] fullResult = new byte[size];
|
|
||||||
|
|
||||||
Span<byte> fullTextureData = flushBuffer.GetTextureData(cbp, this, size, layer, level);
|
|
||||||
|
|
||||||
GetDataFromBuffer(fullTextureData, size, fullTextureData).CopyTo(fullResult);
|
|
||||||
|
|
||||||
return fullResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(MemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
@ -824,7 +769,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
|
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
|
||||||
{
|
{
|
||||||
const int MaxChunkSize = 1024 * 1024 * 96; // 96MB Chunks
|
const int MaxChunkSize = 1024 * 1024;
|
||||||
|
|
||||||
int bufferDataLength = GetBufferDataLength(data.Length);
|
int bufferDataLength = GetBufferDataLength(data.Length);
|
||||||
|
|
||||||
|
@ -29,17 +29,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[Option("exclusive-fullscreen-height", Required = false, Default = 1080, HelpText = "Set vertical resolution for exclusive fullscreen mode.")]
|
[Option("exclusive-fullscreen-height", Required = false, Default = 1080, HelpText = "Set vertical resolution for exclusive fullscreen mode.")]
|
||||||
public int ExclusiveFullscreenHeight { get; set; }
|
public int ExclusiveFullscreenHeight { get; set; }
|
||||||
|
|
||||||
// Host Information
|
|
||||||
|
|
||||||
[Option("device-model", Required = false, HelpText = "Set the current iDevice Model")]
|
|
||||||
public string DeviceModel { get; set; }
|
|
||||||
|
|
||||||
[Option("has-memory-entitlement", Required = false, HelpText = "If the increased memory entitlement exists.")]
|
|
||||||
public bool MemoryEnt { get; set; }
|
|
||||||
|
|
||||||
[Option("device-display-name", Required = false, HelpText = "Set the current iDevice display name.")]
|
|
||||||
public string DisplayName { get; set; }
|
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
|
||||||
[Option("correct-controller", Required = false, Default = false, HelpText = "Makes the on-screen controller (iOS) buttons correspond to what they show.")]
|
[Option("correct-controller", Required = false, Default = false, HelpText = "Makes the on-screen controller (iOS) buttons correspond to what they show.")]
|
||||||
@ -207,7 +196,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[Option("aspect-ratio", Required = false, Default = AspectRatio.Fixed16x9, HelpText = "Aspect Ratio applied to the renderer window.")]
|
[Option("aspect-ratio", Required = false, Default = AspectRatio.Fixed16x9, HelpText = "Aspect Ratio applied to the renderer window.")]
|
||||||
public AspectRatio AspectRatio { get; set; }
|
public AspectRatio AspectRatio { get; set; }
|
||||||
|
|
||||||
[Option("backend-threading", Required = false, Default = BackendThreading.On, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
|
[Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")]
|
||||||
public BackendThreading BackendThreading { get; set; }
|
public BackendThreading BackendThreading { get; set; }
|
||||||
|
|
||||||
[Option("disable-macro-hle", Required = false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")]
|
[Option("disable-macro-hle", Required = false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")]
|
||||||
|
@ -266,6 +266,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "initialize")]
|
[UnmanagedCallersOnly(EntryPoint = "initialize")]
|
||||||
public static unsafe void Initialize()
|
public static unsafe void Initialize()
|
||||||
{
|
{
|
||||||
|
|
||||||
AppDataManager.Initialize(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
|
AppDataManager.Initialize(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
|
||||||
|
|
||||||
if (_virtualFileSystem == null)
|
if (_virtualFileSystem == null)
|
||||||
@ -403,22 +404,10 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
||||||
public static void StopEmulation(bool shouldPause)
|
public static void StopEmulation()
|
||||||
{
|
{
|
||||||
if (_window != null)
|
if (_window != null)
|
||||||
{
|
{
|
||||||
if (!shouldPause)
|
|
||||||
{
|
|
||||||
_window.Device.SetVolume(1);
|
|
||||||
_window._isPaused = false;
|
|
||||||
_window._pauseEvent.Set();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_window.Device.SetVolume(0);
|
|
||||||
_window._isPaused = true;
|
|
||||||
_window._pauseEvent.Reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -898,9 +887,19 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
if (inputId == null)
|
if (inputId == null)
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
|
if (index == PlayerIndex.Player1)
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
|
||||||
|
|
||||||
return null;
|
// Default to keyboard
|
||||||
|
inputId = "0";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IGamepad gamepad;
|
IGamepad gamepad;
|
||||||
@ -991,26 +990,12 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
bool isNintendoStyle = true; // gamepadName.Contains("Nintendo") || gamepadName.Contains("Joycons");
|
bool isNintendoStyle = true; // gamepadName.Contains("Nintendo") || gamepadName.Contains("Joycons");
|
||||||
|
|
||||||
ControllerType currentController;
|
|
||||||
if (index == PlayerIndex.Handheld)
|
|
||||||
{
|
|
||||||
currentController = ControllerType.Handheld;
|
|
||||||
}
|
|
||||||
else if (gamepadName.Contains("Joycons") || gamepadName.Contains("Backbone"))
|
|
||||||
{
|
|
||||||
currentController = ControllerType.JoyconPair;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentController = ControllerType.ProController;
|
|
||||||
}
|
|
||||||
|
|
||||||
config = new StandardControllerInputConfig
|
config = new StandardControllerInputConfig
|
||||||
{
|
{
|
||||||
Version = InputConfig.CurrentVersion,
|
Version = InputConfig.CurrentVersion,
|
||||||
Backend = InputBackendType.GamepadSDL2,
|
Backend = InputBackendType.GamepadSDL2,
|
||||||
Id = null,
|
Id = null,
|
||||||
ControllerType = currentController,
|
ControllerType = ControllerType.JoyconPair,
|
||||||
DeadzoneLeft = 0.1f,
|
DeadzoneLeft = 0.1f,
|
||||||
DeadzoneRight = 0.1f,
|
DeadzoneRight = 0.1f,
|
||||||
RangeLeft = 1.0f,
|
RangeLeft = 1.0f,
|
||||||
@ -1169,11 +1154,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsIOS()) {
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"Current Device: {option.DisplayName} ({option.DeviceModel}) {Environment.OSVersion.Version}");
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"Increased Memory Limit: {option.MemoryEnt}");
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphicsConfig.EnableShaderCache = true;
|
GraphicsConfig.EnableShaderCache = true;
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
||||||
|
@ -44,9 +44,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
_mainThreadActions.Enqueue(action);
|
_mainThreadActions.Enqueue(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool _isPaused;
|
|
||||||
public ManualResetEvent _pauseEvent;
|
|
||||||
|
|
||||||
public NpadManager NpadManager;
|
public NpadManager NpadManager;
|
||||||
public TouchScreenManager TouchScreenManager;
|
public TouchScreenManager TouchScreenManager;
|
||||||
public Switch Device;
|
public Switch Device;
|
||||||
@ -107,7 +104,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||||
_exitEvent = new ManualResetEvent(false);
|
_exitEvent = new ManualResetEvent(false);
|
||||||
_gpuDoneEvent = new ManualResetEvent(false);
|
_gpuDoneEvent = new ManualResetEvent(false);
|
||||||
_pauseEvent = new ManualResetEvent(true);
|
|
||||||
_aspectRatio = aspectRatio;
|
_aspectRatio = aspectRatio;
|
||||||
_enableMouse = enableMouse;
|
_enableMouse = enableMouse;
|
||||||
HostUITheme = new HeadlessHostUiTheme();
|
HostUITheme = new HeadlessHostUiTheme();
|
||||||
@ -302,8 +298,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pauseEvent.WaitOne();
|
|
||||||
|
|
||||||
_ticks += _chrono.ElapsedTicks;
|
_ticks += _chrono.ElapsedTicks;
|
||||||
|
|
||||||
_chrono.Restart();
|
_chrono.Restart();
|
||||||
@ -384,6 +378,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
|
|
||||||
UpdateFrame();
|
UpdateFrame();
|
||||||
|
|
||||||
SDL_PumpEvents();
|
SDL_PumpEvents();
|
||||||
|
@ -9,18 +9,8 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
|
||||||
private readonly List<string> _gamepadsIds;
|
private readonly List<string> _gamepadsIds;
|
||||||
private readonly object _lock = new object();
|
|
||||||
|
|
||||||
public ReadOnlySpan<string> GamepadsIds
|
public ReadOnlySpan<string> GamepadsIds => _gamepadsIds.ToArray();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
return _gamepadsIds.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DriverName => "SDL2";
|
public string DriverName => "SDL2";
|
||||||
|
|
||||||
@ -45,7 +35,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GenerateGamepadId(int joystickIndex)
|
private static string GenerateGamepadId(int joystickIndex)
|
||||||
{
|
{
|
||||||
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
|
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
|
||||||
|
|
||||||
@ -54,16 +44,14 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include joystickIndex at the start of the ID to maintain compatibility with GetJoystickIndexByGamepadId
|
|
||||||
return joystickIndex + "-" + guid;
|
return joystickIndex + "-" + guid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetJoystickIndexByGamepadId(string id)
|
private static int GetJoystickIndexByGamepadId(string id)
|
||||||
{
|
{
|
||||||
string[] data = id.Split("-");
|
string[] data = id.Split("-");
|
||||||
|
|
||||||
// Parse the joystick index from the ID string
|
if (data.Length != 6 || !int.TryParse(data[0], out int joystickIndex))
|
||||||
if (data.Length < 2 || !int.TryParse(data[0], out int joystickIndex))
|
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -76,11 +64,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
|
if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
|
||||||
{
|
{
|
||||||
_gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
|
_gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
|
||||||
|
_gamepadsIds.Remove(id);
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_gamepadsIds.Remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
}
|
}
|
||||||
@ -90,13 +74,6 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
|
||||||
{
|
{
|
||||||
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
|
|
||||||
{
|
|
||||||
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
|
||||||
// so it is rejected to avoid doubling the entries.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string id = GenerateGamepadId(joystickDeviceId);
|
string id = GenerateGamepadId(joystickDeviceId);
|
||||||
|
|
||||||
if (id == null)
|
if (id == null)
|
||||||
@ -104,21 +81,16 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we already have this gamepad ID in our list
|
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
||||||
lock (_lock)
|
// so it is rejected to avoid doubling the entries.
|
||||||
|
if (_gamepadsIds.Contains(id))
|
||||||
{
|
{
|
||||||
if (_gamepadsIds.Contains(id))
|
return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
|
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
|
||||||
{
|
{
|
||||||
lock (_lock)
|
_gamepadsIds.Add(id);
|
||||||
{
|
|
||||||
_gamepadsIds.Add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnGamepadConnected?.Invoke(id);
|
OnGamepadConnected?.Invoke(id);
|
||||||
}
|
}
|
||||||
@ -132,16 +104,12 @@ namespace Ryujinx.Input.SDL2
|
|||||||
SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
|
SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
|
||||||
SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
|
SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
|
||||||
|
|
||||||
// Simulate a full disconnect when disposing
|
|
||||||
foreach (string id in _gamepadsIds)
|
foreach (string id in _gamepadsIds)
|
||||||
{
|
{
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_lock)
|
_gamepadsIds.Clear();
|
||||||
{
|
|
||||||
_gamepadsIds.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL2Driver.Instance.Dispose();
|
SDL2Driver.Instance.Dispose();
|
||||||
}
|
}
|
||||||
@ -162,6 +130,11 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id != GenerateGamepadId(joystickIndex))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex);
|
||||||
|
|
||||||
if (gamepadHandle == IntPtr.Zero)
|
if (gamepadHandle == IntPtr.Zero)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user