mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-04-24 08:25:14 +00:00
Add Fixed Handheld mode, Location to keep game running in the background, New Airplay Menu amd more
This commit is contained in:
parent
15171a703a
commit
e382a35387
@ -27,7 +27,7 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"buildVersion": "1",
|
"buildVersion": "1",
|
||||||
"date": "2025-04-8",
|
"date": "2025-04-08",
|
||||||
"localizedDescription": "First AltStore release!",
|
"localizedDescription": "First AltStore release!",
|
||||||
"downloadURL": "https://git.743378673.xyz/MeloNX/MeloNX/releases/download/1.7.0/MeloNX.ipa",
|
"downloadURL": "https://git.743378673.xyz/MeloNX/MeloNX/releases/download/1.7.0/MeloNX.ipa",
|
||||||
"size": 79821,
|
"size": 79821,
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"com.apple.developer.kernel.increased-memory-limit"
|
"com.apple.developer.kernel.increased-memory-limit"
|
||||||
],
|
],
|
||||||
"privacy": {
|
"privacy": {
|
||||||
"NSPhotoLibraryAddUsageDescription": "MeloNX needs access to your Photo Library in order to save images."
|
"NSPhotoLibraryAddUsageDescription": "MeloNX needs access to your Photo Library in order to save screenshots."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ using ARMeilleure.CodeGen.Unwinding;
|
|||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.Native;
|
using ARMeilleure.Native;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -16,73 +15,52 @@ 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 - 4;
|
private static readonly int _pageMask = _pageSize - 1;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 128 * 1024 * 1024;
|
private const int CacheSize = 2047 * 1024 * 1024;
|
||||||
private const int CacheSizeIOS = 128 * 1024 * 1024;
|
private const int CacheSizeIOS = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
private static ReservedRegion _jitRegion;
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
|
||||||
private static List<CacheMemoryAllocator> _cacheAllocators = [];
|
private static CacheMemoryAllocator _cacheAllocator;
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsWindows())
|
return;
|
||||||
{
|
|
||||||
// JitUnwindWindows.RemoveFunctionTableHandler(
|
|
||||||
// _jitRegions[0].Pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < _jitRegions.Count; i++)
|
|
||||||
{
|
|
||||||
_jitRegions[i].Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_jitRegions.Clear();
|
|
||||||
_cacheAllocators.Clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_initialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_activeRegionIndex = 0;
|
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
|
||||||
|
|
||||||
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(
|
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
|
||||||
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@ -95,9 +73,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
while (_deferredRxProtect.TryDequeue(out var result))
|
while (_deferredRxProtect.TryDequeue(out var result))
|
||||||
{
|
{
|
||||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
ReprotectAsExecutable(result.funcOffset, result.length);
|
||||||
|
|
||||||
ReprotectAsExecutable(targetRegion, result.funcOffset, result.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,8 +87,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
int funcOffset = Allocate(code.Length, deferProtect);
|
int funcOffset = Allocate(code.Length, deferProtect);
|
||||||
|
|
||||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
|
||||||
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
|
|
||||||
|
|
||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
@ -123,7 +98,8 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
ReprotectAsExecutable(funcOffset, code.Length);
|
||||||
|
|
||||||
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,9 +115,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
ReprotectAsWritable(funcOffset, code.Length);
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
ReprotectAsExecutable(funcOffset, code.Length);
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@ -163,50 +139,41 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
// return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var region in _jitRegions)
|
Debug.Assert(_initialized);
|
||||||
|
|
||||||
|
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||||
|
|
||||||
|
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||||
{
|
{
|
||||||
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
_cacheEntries.RemoveAt(entryIndex);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
|
||||||
|
|
||||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
|
||||||
{
|
|
||||||
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
|
|
||||||
_cacheEntries.RemoveAt(entryIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
private static void ReprotectAsWritable(int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
private static void ReprotectAsExecutable(int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int Allocate(int codeSize, bool deferProtect = false)
|
private static int Allocate(int codeSize, bool deferProtect = false)
|
||||||
@ -220,33 +187,18 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
alignment = 0x4000;
|
alignment = 0x4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(ref codeSize, alignment);
|
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
||||||
|
|
||||||
if (allocOffset >= 0)
|
Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
|
||||||
|
|
||||||
|
if (allocOffset < 0)
|
||||||
{
|
{
|
||||||
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||||
return allocOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int exhaustedRegion = _activeRegionIndex;
|
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||||
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
|
|
||||||
_jitRegions.Add(newRegion);
|
|
||||||
_activeRegionIndex = _jitRegions.Count - 1;
|
|
||||||
|
|
||||||
int newRegionNumber = _activeRegionIndex;
|
return allocOffset;
|
||||||
|
|
||||||
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)
|
||||||
@ -299,4 +251,4 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -724,6 +724,10 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -896,6 +900,16 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(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;
|
||||||
@ -919,7 +933,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 = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 4D52P7Z7YN;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@ -1000,6 +1014,10 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -1172,9 +1190,19 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(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-personal";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
Binary file not shown.
@ -50,7 +50,7 @@ char* installed_firmware_version();
|
|||||||
|
|
||||||
void set_native_window(void *layerPtr);
|
void set_native_window(void *layerPtr);
|
||||||
|
|
||||||
void stop_emulation();
|
void stop_emulation(bool shouldPause);
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
@ -371,7 +371,6 @@ class Ryujinx : ObservableObject {
|
|||||||
self.emulationUIView = nil
|
self.emulationUIView = nil
|
||||||
self.metalLayer = nil
|
self.metalLayer = nil
|
||||||
|
|
||||||
stop_emulation()
|
|
||||||
thread.cancel()
|
thread.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ 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
|
||||||
@ -159,16 +160,7 @@ struct ContentView: View {
|
|||||||
initControllerObservers()
|
initControllerObservers()
|
||||||
|
|
||||||
Air.play(AnyView(
|
Air.play(AnyView(
|
||||||
VStack {
|
ControllerListView(game: $game)
|
||||||
Image(systemName: "gamecontroller")
|
|
||||||
.font(.system(size: 300))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
Text("Select Game")
|
|
||||||
.font(.system(size: 150))
|
|
||||||
.bold()
|
|
||||||
}
|
|
||||||
))
|
))
|
||||||
|
|
||||||
checkJitStatus()
|
checkJitStatus()
|
||||||
@ -310,6 +302,8 @@ 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 {
|
||||||
@ -327,14 +321,34 @@ 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 controllersList.count == 1 {
|
if !currentControllers.isEmpty, !(currentControllers.count == 1) {
|
||||||
currentControllers.append(controllersList[0])
|
var currentController: [Controller] = []
|
||||||
} else if (controllersList.count - 1) >= 1 {
|
|
||||||
for controller in controllersList {
|
if currentController.count == 1 {
|
||||||
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
currentController.append(controllersList[0])
|
||||||
currentControllers.append(controller)
|
} else if (controllersList.count - 1) >= 1 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -397,10 +411,12 @@ 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" {
|
||||||
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
DispatchQueue.main.async {
|
||||||
game = ryujinx.games.first(where: { $0.titleId == text })
|
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
||||||
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
game = ryujinx.games.first(where: { $0.titleId == text })
|
||||||
game = ryujinx.games.first(where: { $0.titleName == text })
|
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
||||||
|
game = ryujinx.games.first(where: { $0.titleName == text })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,3 +429,136 @@ 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,9 @@ 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 {
|
||||||
@ -26,7 +29,7 @@ struct EmulationView: View {
|
|||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
Air.play(AnyView(MetalView().ignoresSafeArea().edgesIgnoringSafeArea(.all)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MetalView() // The Emulation View
|
MetalView() // The Emulation View
|
||||||
@ -88,6 +91,7 @@ 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
|
||||||
@ -95,5 +99,18 @@ struct EmulationView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -429,61 +429,16 @@ struct SettingsView: View {
|
|||||||
Text("Controller Selection")
|
Text("Controller Selection")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
Divider()
|
|
||||||
|
|
||||||
if !currentControllers.isEmpty {
|
if currentControllers.isEmpty {
|
||||||
ForEach(currentControllers) { controller in
|
emptyControllersView
|
||||||
if currentControllers.firstIndex(of: controller) == currentControllers.count - 1 && currentControllers.count != 1 {
|
} else {
|
||||||
Divider()
|
controllerListView
|
||||||
}
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "gamecontroller.fill")
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
|
|
||||||
if let index = currentControllers.firstIndex(where: { $0.id == controller.id }) {
|
|
||||||
Text("Player \(index + 1): \(controller.name)")
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Button {
|
|
||||||
toggleController(controller)
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentControllers[0] != controller && currentControllers.firstIndex(of: controller) == currentControllers.count - 1 {
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onMove { from, to in
|
|
||||||
currentControllers.move(fromOffsets: from, toOffset: to)
|
|
||||||
}
|
|
||||||
.environment(\.editMode, .constant(.active))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !controllersList.filter({ !currentControllers.contains($0) }).isEmpty {
|
if hasAvailableControllers {
|
||||||
Divider()
|
Divider()
|
||||||
|
addControllerButton
|
||||||
Menu {
|
|
||||||
ForEach(controllersList.filter { !currentControllers.contains($0) }) { controller in
|
|
||||||
Button {
|
|
||||||
currentControllers.append(controller)
|
|
||||||
} label: {
|
|
||||||
Text(controller.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Label("Add Controller", systemImage: "plus.circle.fill")
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
|
||||||
.padding(.vertical, 6)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -556,6 +511,78 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Controller Selection Components
|
||||||
|
|
||||||
|
private var hasAvailableControllers: Bool {
|
||||||
|
!controllersList.filter { !currentControllers.contains($0) }.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
private var emptyControllersView: some View {
|
||||||
|
HStack {
|
||||||
|
Text("No controllers selected (Keyboard will be used)")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.italic()
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var controllerListView: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
ForEach(currentControllers.indices, id: \.self) { index in
|
||||||
|
let controller = currentControllers[index]
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: "gamecontroller.fill")
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
|
||||||
|
Text("Player \(index + 1): \(controller.name)")
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
toggleController(controller)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
|
if index < currentControllers.count - 1 {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onMove { from, to in
|
||||||
|
currentControllers.move(fromOffsets: from, toOffset: to)
|
||||||
|
}
|
||||||
|
.environment(\.editMode, .constant(.active))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var addControllerButton: some View {
|
||||||
|
Menu {
|
||||||
|
ForEach(controllersList.filter { !currentControllers.contains($0) }) { controller in
|
||||||
|
Button {
|
||||||
|
currentControllers.append(controller)
|
||||||
|
} label: {
|
||||||
|
Text(controller.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label("Add Controller", systemImage: "plus.circle.fill")
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
.padding(.vertical, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - System Settings
|
// MARK: - System Settings
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ import SwiftUI
|
|||||||
import UIKit
|
import UIKit
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
|
||||||
extension UIDocumentPickerViewController {
|
extension UIDocumentPickerViewController {
|
||||||
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
|
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
|
||||||
@ -29,6 +31,8 @@ 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
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
if finishedStorage {
|
if finishedStorage {
|
||||||
|
@ -38,8 +38,9 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>location</string>
|
||||||
<string>processing</string>
|
<string>processing</string>
|
||||||
|
<string>audio</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
@ -403,10 +403,22 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
||||||
public static void StopEmulation()
|
public static void StopEmulation(bool shouldPause)
|
||||||
{
|
{
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,19 +898,9 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
if (inputId == null)
|
if (inputId == null)
|
||||||
{
|
{
|
||||||
if (index == PlayerIndex.Player1)
|
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
|
|
||||||
|
|
||||||
// Default to keyboard
|
return null;
|
||||||
inputId = "0";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IGamepad gamepad;
|
IGamepad gamepad;
|
||||||
@ -989,12 +991,26 @@ 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 = ControllerType.JoyconPair,
|
ControllerType = currentController,
|
||||||
DeadzoneLeft = 0.1f,
|
DeadzoneLeft = 0.1f,
|
||||||
DeadzoneRight = 0.1f,
|
DeadzoneRight = 0.1f,
|
||||||
RangeLeft = 1.0f,
|
RangeLeft = 1.0f,
|
||||||
|
@ -44,6 +44,9 @@ 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;
|
||||||
@ -104,6 +107,7 @@ 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();
|
||||||
@ -298,6 +302,8 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pauseEvent.WaitOne();
|
||||||
|
|
||||||
_ticks += _chrono.ElapsedTicks;
|
_ticks += _chrono.ElapsedTicks;
|
||||||
|
|
||||||
_chrono.Restart();
|
_chrono.Restart();
|
||||||
@ -378,7 +384,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
|
|
||||||
UpdateFrame();
|
UpdateFrame();
|
||||||
|
|
||||||
SDL_PumpEvents();
|
SDL_PumpEvents();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user