1
0
forked from MeloNX/MeloNX

Compare commits

...

4 Commits

10 changed files with 206 additions and 212 deletions

View File

@ -60,6 +60,8 @@ If having Issues installing firmware (Make sure your Keys are installed first)
### Xcode
**NOTE: These Xcode builds are nightly and may have unfinished features.**
1. **Compile Guide**
- Visit the [guide here](https://git.743378673.xyz/MeloNX/MeloNX/src/branch/XC-ios-ht/Compile.md).

View File

@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
public IJitMemoryBlock Block { get; }
public IJitMemoryAllocator Allocator { get; }
public IntPtr Pointer => Block.Pointer;
@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
granularity = DefaultGranularity;
}
Allocator = allocator;
Block = allocator.Reserve(maxSize);
_maxSize = maxSize;
_sizeGranularity = granularity;

View File

@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory;
using ARMeilleure.Native;
using Ryujinx.Memory;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -18,7 +19,7 @@ namespace ARMeilleure.Translation.Cache
private static readonly int _pageMask = _pageSize - 4;
private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 1024 * 1024 * 1024;
private const int CacheSize = 128 * 1024 * 1024;
private const int CacheSizeIOS = 128 * 1024 * 1024;
private static ReservedRegion _jitRegion;
@ -31,6 +32,10 @@ namespace ARMeilleure.Translation.Cache
private static readonly object _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
@ -49,7 +54,11 @@ namespace ARMeilleure.Translation.Cache
return;
}
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
{
@ -60,7 +69,9 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
}
_initialized = true;
@ -73,7 +84,9 @@ namespace ARMeilleure.Translation.Cache
{
while (_deferredRxProtect.TryDequeue(out var result))
{
ReprotectAsExecutable(result.funcOffset, result.length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
ReprotectAsExecutable(targetRegion, result.funcOffset, result.length);
}
}
@ -87,22 +100,15 @@ namespace ARMeilleure.Translation.Cache
int funcOffset = Allocate(code.Length, deferProtect);
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsIOS())
{
Marshal.Copy(code, 0, funcPtr, code.Length);
if (deferProtect)
{
_deferredRxProtect.Enqueue((funcOffset, code.Length));
}
else
{
ReprotectAsExecutable(funcOffset, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
}
}
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
unsafe
@ -115,9 +121,9 @@ namespace ARMeilleure.Translation.Cache
}
else
{
ReprotectAsWritable(funcOffset, code.Length);
ReprotectAsWritable(targetRegion, funcOffset, 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)
{
@ -139,41 +145,50 @@ namespace ARMeilleure.Translation.Cache
{
if (OperatingSystem.IsIOS())
{
return;
// return;
}
lock (_lock)
{
Debug.Assert(_initialized);
foreach (var region in _jitRegions)
{
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
return;
}
}
}
private static void ReprotectAsWritable(int offset, int size)
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_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 regionStart = offset & ~_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)
@ -187,20 +202,35 @@ namespace ARMeilleure.Translation.Cache
alignment = 0x4000;
}
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
//DEBUG: Show JIT Memory Allocation
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
if (allocOffset < 0)
if (allocOffset >= 0)
{
throw new OutOfMemoryException("JIT Cache exhausted.");
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
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;
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(ref codeSize, alignment);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
}
private static int AlignCodeSize(int codeSize, bool deferProtect = false)

View File

@ -12,12 +12,12 @@
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
<integer>1</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>2</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -77,6 +77,7 @@ class Ryujinx {
var disablevsync: Bool
var language: SystemLanguage
var regioncode: SystemRegionCode
var handHeldController: Bool
init(gamepath: String,
@ -102,7 +103,8 @@ class Ryujinx {
disablePTC: Bool = false,
disablevsync: Bool = false,
language: SystemLanguage = .americanEnglish,
regioncode: SystemRegionCode = .usa
regioncode: SystemRegionCode = .usa,
handHeldController: Bool = false
) {
self.gamepath = gamepath
self.inputids = inputids
@ -128,6 +130,7 @@ class Ryujinx {
self.disablevsync = disablevsync
self.language = language
self.regioncode = regioncode
self.handHeldController = handHeldController
}
}
@ -321,12 +324,16 @@ class Ryujinx {
args.append("--list-inputs-ids")
}
// Append the input ids (limit to 4 just in case)
// Append the input ids (limit to 8 (used to be 4) just in case)
if !config.inputids.isEmpty {
config.inputids.prefix(4).enumerated().forEach { index, inputId in
config.inputids.prefix(8).enumerated().forEach { index, inputId in
if config.handHeldController {
args.append(contentsOf: ["\(index == 0 ? "--input-id-handheld" : "--input-id-\(index + 1)")", inputId])
} else {
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
}
}
}
// Apped any additional arguments
args.append(contentsOf: config.additionalArgs)
@ -477,23 +484,21 @@ class Ryujinx {
func repeatuntilfindLayer() {
DispatchQueue.global(qos: .background).async {
Task { @MainActor in
while self.metalLayer == nil {
let layer = self.getMetalLayer(nil)
if layer != nil {
DispatchQueue.main.async {
self.metalLayer = layer
}
break
}
Thread.sleep(forTimeInterval: 0.1)
try await Task.sleep(nanoseconds: 100_000_000)
}
}
}
@MainActor
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
var window = window
if window == nil {

View File

@ -9,7 +9,7 @@ import SwiftUI
import UniformTypeIdentifiers
public struct Game: Identifiable, Equatable, Hashable {
public var id = UUID()
public var id: String { titleId }
var containerFolder: URL
var fileType: UTType
@ -21,7 +21,6 @@ public struct Game: Identifiable, Equatable, Hashable {
var version: String
var icon: UIImage?
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game {
var gameInfo = gameInfo
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")

View File

@ -39,7 +39,9 @@ struct GameLibraryView: View {
var filteredGames: [Game] {
if searchText.isEmpty {
return Ryujinx.shared.games
return Ryujinx.shared.games.filter { game in
!realRecentGames.contains(where: { $0.titleId == game.titleId })
}
}
return Ryujinx.shared.games.filter {
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
@ -47,6 +49,13 @@ struct GameLibraryView: View {
}
}
var realRecentGames: [Game] {
let games = Ryujinx.shared.games
return recentGames.compactMap { recentGame in
games.first(where: { $0.titleId == recentGame.titleId })
}
}
var body: some View {
iOSNav {
List {
@ -66,46 +75,32 @@ struct GameLibraryView: View {
.frame(maxWidth: .infinity)
.padding(.top, 40)
} else {
if !isSearching && !recentGames.isEmpty {
VStack(alignment: .leading, spacing: 12) {
if !isSearching && !realRecentGames.isEmpty {
Section {
ForEach(realRecentGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
removeFromRecentGames(game)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
} header: {
Text("Recent")
.font(.title2.bold())
.padding(.horizontal)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 16) {
ForEach(recentGames) { game in
RecentGameCard(game: game, startemu: $startemu)
.onTapGesture {
addToRecentGames(game)
startemu = game
}
}
}
.padding(.horizontal)
}
}
VStack(alignment: .leading, spacing: 12) {
Text("All Games")
.font(.title2.bold())
.padding(.horizontal)
LazyVStack(spacing: 2) {
Section {
ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
.onTapGesture {
addToRecentGames(game)
}
}
}
} header: {
Text("Others")
}
} else {
ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameInfo: $gameInfo)
.onTapGesture {
addToRecentGames(game)
}
}
}
}
@ -206,8 +201,13 @@ struct GameLibraryView: View {
}
}
}
.onChange(of: startemu) { game in
guard let game else { return }
addToRecentGames(game)
}
}
.searchable(text: $searchText)
.animation(.easeInOut, value: searchText)
.onChange(of: searchText) { _ in
isSearching = !searchText.isEmpty
}
@ -291,7 +291,7 @@ struct GameLibraryView: View {
}
private func addToRecentGames(_ game: Game) {
recentGames.removeAll { $0.id == game.id }
recentGames.removeAll { $0.titleId == game.titleId }
recentGames.insert(game, at: 0)
@ -302,6 +302,11 @@ struct GameLibraryView: View {
saveRecentGames()
}
private func removeFromRecentGames(_ game: Game) {
recentGames.removeAll { $0.titleId == game.titleId }
saveRecentGames()
}
private func saveRecentGames() {
do {
let encoder = JSONEncoder()
@ -364,53 +369,6 @@ extension Game: Codable {
}
}
// MARK: - Recent Game Card
struct RecentGameCard: View {
let game: Game
@Binding var startemu: Game?
@Environment(\.colorScheme) var colorScheme
var body: some View {
Button(action: {
startemu = game
}) {
VStack(alignment: .leading, spacing: 8) {
if let icon = game.icon {
Image(uiImage: icon)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 140, height: 140)
.cornerRadius(12)
} else {
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(colorScheme == .dark ?
Color(.systemGray5) : Color(.systemGray6))
.frame(width: 140, height: 140)
Image(systemName: "gamecontroller.fill")
.font(.system(size: 40))
.foregroundColor(.gray)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(game.titleName)
.font(.subheadline.bold())
.lineLimit(1)
Text(game.developer)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}
.padding(.horizontal, 4)
}
}
.buttonStyle(.plain)
}
}
// MARK: - Game List Item
struct GameListRow: View {
let game: Game
@ -467,6 +425,7 @@ struct GameListRow: View {
.foregroundColor(.accentColor)
.opacity(0.8)
}
}
.contextMenu {
Section {
Button {
@ -508,8 +467,6 @@ struct GameListRow: View {
}
}
}
}
.buttonStyle(.plain)
.confirmationDialog("Are you sure you want to delete this game?", isPresented: $showGameDeleteConfirmation) {
Button("Delete", role: .destructive) {
if let game = gametoDelete {

View File

@ -272,42 +272,12 @@ struct SettingsView: View {
Text("Select input devices and on-screen controls to play with. ")
}
// Language and Region Settings
Section {
Picker(selection: $config.language) {
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Language", iconName: "character.bubble")
}
Picker(selection: $config.regioncode) {
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Region", iconName: "globe")
}
// globe
} header: {
Text("Language and Region Settings")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Configure the System Language and the Region.")
}
// Input Settings
Section {
Toggle(isOn: $config.macroHLE) {
labelWithIcon("Player 1 to Handheld Input", iconName: "formfitting.gamecontroller")
}.tint(.blue)
Toggle(isOn: $config.listinputids) {
labelWithIcon("List Input IDs", iconName: "list.bullet")
}
.tint(.blue)
Toggle(isOn: $ryuDemo) {
labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw")
@ -363,6 +333,35 @@ struct SettingsView: View {
Text("Configure input devices and on-screen controls for easier navigation and play.")
}
// Language and Region Settings
Section {
Picker(selection: $config.language) {
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Language", iconName: "character.bubble")
}
Picker(selection: $config.regioncode) {
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
Text(ratio.displayName).tag(ratio)
}
} label: {
labelWithIcon("Region", iconName: "globe")
}
// globe
} header: {
Text("Language and Region Settings")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Configure the System Language and the Region.")
}
// CPU Mode
Section {
if filteredMemoryModes.isEmpty {

View File

@ -45,7 +45,7 @@ struct DLCManagerSheet: View {
Self.saveDlcs(game, dlc: dlcs)
}) {
HStack {
Text(dlc.containerPath)
Text((dlc.containerPath as NSString).lastPathComponent)
.foregroundStyle(Color(uiColor: .label))
Spacer()
if dlc.downloadableContentNcaList.first?.enabled == true {