Add On-Screen controller and Fix Internet and Lan Multiplayer

This commit is contained in:
Stossy11 2024-11-29 00:01:33 +11:00
parent b2424a9652
commit 9a86b2000a
14 changed files with 494 additions and 215 deletions

View File

@ -59,7 +59,7 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */ /* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
4E80AA0A2CD6FAA800029585 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = { 4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet; isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = { attributesByRelativePath = {
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, ); "Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
@ -68,13 +68,13 @@
"Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, ); "Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, );
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libSPIRV.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libteakra.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
}; };
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */; buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
membershipExceptions = ( membershipExceptions = (
@ -86,9 +86,9 @@
Dependencies/XCFrameworks/libavfilter.xcframework, Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework, Dependencies/XCFrameworks/libavformat.xcframework,
Dependencies/XCFrameworks/libavutil.xcframework, Dependencies/XCFrameworks/libavutil.xcframework,
Dependencies/XCFrameworks/libSPIRV.xcframework,
Dependencies/XCFrameworks/libswresample.xcframework, Dependencies/XCFrameworks/libswresample.xcframework,
Dependencies/XCFrameworks/libswscale.xcframework, Dependencies/XCFrameworks/libswscale.xcframework,
Dependencies/XCFrameworks/libteakra.xcframework,
Dependencies/XCFrameworks/MoltenVK.xcframework, Dependencies/XCFrameworks/MoltenVK.xcframework,
Dependencies/XCFrameworks/SDL2.xcframework, Dependencies/XCFrameworks/SDL2.xcframework,
); );
@ -100,7 +100,7 @@
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = ( exceptions = (
4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */, 4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */,
4E80AA0A2CD6FAA800029585 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */, 4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */,
); );
path = MeloNX; path = MeloNX;
sourceTree = "<group>"; sourceTree = "<group>";
@ -542,6 +542,18 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
@ -622,6 +634,18 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;

View File

@ -55,7 +55,7 @@
</Testables> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Release" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"

View File

@ -23,6 +23,7 @@ class MTLHud {
static let shared = MTLHud() static let shared = MTLHud()
private init() { private init() {
openMetalDylib()
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") { if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
enable() enable()
} else { } else {

View File

@ -0,0 +1,99 @@
//
// VirtualController.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
public var controllerCallback: (() -> Void)?
var VirtualController: GCVirtualController!
func showVirtualController() {
let config = GCVirtualController.Configuration()
if UserDefaults.standard.bool(forKey: "RyuDemoControls") {
config.elements = [
GCInputLeftThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX,
GCInputButtonY,
// GCInputRightThumbstick,
GCInputRightTrigger,
GCInputLeftTrigger,
GCInputLeftShoulder,
GCInputRightShoulder
]
} else {
config.elements = [
GCInputLeftThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX,
GCInputButtonY,
GCInputRightThumbstick,
GCInputRightTrigger,
GCInputLeftTrigger,
GCInputLeftShoulder,
GCInputRightShoulder
]
}
VirtualController = GCVirtualController(configuration: config)
VirtualController.connect { err in
print("controller connect: \(String(describing: err))")
patchMakeKeyAndVisible()
if let controllerCallback {
controllerCallback()
}
}
}
func waitforcontroller() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
if let window = UIApplication.shared.windows.first {
// Function to recursively search for GCControllerView
func findGCControllerView(in view: UIView) -> UIView? {
// Check if current view is GCControllerView
if String(describing: type(of: view)) == "GCControllerView" {
return view
}
// Search through subviews
for subview in view.subviews {
if let found = findGCControllerView(in: subview) {
return found
}
}
return nil
}
if let gcControllerView = findGCControllerView(in: window) {
// Found the GCControllerView
print("Found GCControllerView:", gcControllerView)
if let theWindow = theWindow, (findGCControllerView(in: theWindow) == nil) {
theWindow.addSubview(gcControllerView)
theWindow.bringSubviewToFront(gcControllerView)
}
}
}
}
}
@available(iOS 15.0, *)
func reconnectVirtualController() {
VirtualController.disconnect()
DispatchQueue.main.async {
VirtualController.connect { err in
print("reconnected: err \(String(describing: err))")
}
}
}

View File

@ -0,0 +1,40 @@
//
// Untitled.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
var theWindow: UIWindow? = nil
extension UIWindow {
@objc func wdb_makeKeyAndVisible() {
if #available(iOS 13.0, *) {
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
}
self.wdb_makeKeyAndVisible()
theWindow = self
if #available(iOS 15.0, *) {
reconnectVirtualController()
}
if let window = theWindow {
waitforcontroller()
}
}
}
func patchMakeKeyAndVisible() {
let uiwindowClass = UIWindow.self
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
method_exchangeImplementations(m1, m2)
}
}

View File

@ -44,6 +44,8 @@ class Ryujinx {
var resscale: Float var resscale: Float
var debuglogs: Bool var debuglogs: Bool
var tracelogs: Bool var tracelogs: Bool
var nintendoinput: Bool
var enableInternet: Bool
var listinputids: Bool var listinputids: Bool
var fullscreen: Bool var fullscreen: Bool
var memoryManagerMode: String var memoryManagerMode: String
@ -64,6 +66,8 @@ class Ryujinx {
disableVSync: Bool = false, disableVSync: Bool = false,
disableShaderCache: Bool = false, disableShaderCache: Bool = false,
disableDockedMode: Bool = false, disableDockedMode: Bool = false,
nintendoinput: Bool = true,
enableInternet: Bool = false,
enableTextureRecompression: Bool = true, enableTextureRecompression: Bool = true,
additionalArgs: [String] = [], additionalArgs: [String] = [],
resscale: Float = 1.00 resscale: Float = 1.00
@ -81,6 +85,8 @@ class Ryujinx {
self.additionalArgs = additionalArgs self.additionalArgs = additionalArgs
self.memoryManagerMode = memoryManagerMode self.memoryManagerMode = memoryManagerMode
self.resscale = resscale self.resscale = resscale
self.nintendoinput = nintendoinput
self.enableInternet = enableInternet
} }
} }
@ -151,6 +157,13 @@ class Ryujinx {
args.append(contentsOf: ["--resolution-scale", String(config.resscale)]) args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
} }
if config.nintendoinput {
args.append("--correct-ons-controller")
}
if config.enableInternet {
args.append("--enable-internet-connection")
}
// Adding default args directly into additionalArgs // Adding default args directly into additionalArgs
if config.disableVSync { if config.disableVSync {
args.append("--disable-vsync") args.append("--disable-vsync")

View File

@ -16,196 +16,230 @@ struct MoltenVKSettings: Codable, Hashable {
} }
struct ContentView: View { struct ContentView: View {
@State public var theWindow: UIWindow? = nil // MARK: - Properties
@State private var theWindow: UIWindow?
@State private var virtualController: GCVirtualController? @State private var virtualController: GCVirtualController?
@State var game: URL? = nil @State private var game: URL?
@State var controllersList: [Controller] = [] @State private var controllersList: [Controller] = []
@State var currentControllers: [Controller] = [] @State private var currentControllers: [Controller] = []
@State var config: Ryujinx.Configuration = Ryujinx.Configuration(gamepath: "") @State private var config: Ryujinx.Configuration
@State private var settings: [MoltenVKSettings]
@State var settings: [MoltenVKSettings] = [ @State private var isVirtualControllerActive: Bool = false
// MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: ""),
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1")
]
// MARK: - Initialization
init() { init() {
// Initialize SDL let defaultConfig = Ryujinx.Configuration(gamepath: "")
DispatchQueue.main.async { [self] in _config = State(initialValue: defaultConfig)
setMoltenVKSettings()
SDL_SetMainReady() let defaultSettings: [MoltenVKSettings] = [
SDL_iPhoneSetEventPump(SDL_TRUE) MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"),
SDL_Init(SDL_INIT_VIDEO) MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"),
patchMakeKeyAndVisible() MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1")
]
_settings = State(initialValue: defaultSettings)
initializeSDL()
}
// MARK: - Body
var body: some View {
iOSNav {
if let game {
emulationView
} else {
mainMenuView
}
}
.onChange(of: isVirtualControllerActive) { newValue in
if newValue {
createVirtualController()
} else {
destroyVirtualController()
}
} }
} }
func setupVirtualController() { // MARK: - View Components
private var emulationView: some View {
ZStack {}
.onAppear {
setupEmulation()
}
}
private var mainMenuView: some View {
HStack {
GameListView(startemu: $game)
.onAppear {
createVirtualController()
refreshControllersList()
}
settingsListView
}
}
private var settingsListView: some View {
List {
Section("Settings") {
NavigationLink("Config") {
SettingsView(config: $config, MoltenVKSettings: $settings)
}
}
Section("Controller") {
Button("Refresh", action: refreshControllersList)
ForEach(controllersList, id: \.self) { controller in
if controller.name != "Apple Touch Controller" {
controllerRow(for: controller)
}
}
}
}
}
private func controllerRow(for controller: Controller) -> some View {
HStack {
Button(controller.name) {
toggleController(controller)
}
Spacer()
if currentControllers.contains(where: { $0.id == controller.id }) {
Image(systemName: "checkmark.circle.fill")
}
}
}
// MARK: - Controller Management
private func createVirtualController() {
let configuration = GCVirtualController.Configuration() let configuration = GCVirtualController.Configuration()
configuration.elements = [ configuration.elements = [
/*
GCInputLeftThumbstick, GCInputLeftThumbstick,
GCInputRightThumbstick, GCInputRightThumbstick,
GCInputButtonA, GCInputButtonA,
GCInputButtonB, GCInputButtonB,
GCInputButtonX, GCInputButtonX,
GCInputButtonY GCInputButtonY,
*/
] ]
let controller = GCVirtualController(configuration: configuration) virtualController = GCVirtualController(configuration: configuration)
self.virtualController = controller virtualController?.connect()
self.virtualController?.connect()
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
} }
var body: some View { private func destroyVirtualController() {
iOSNav { virtualController?.disconnect()
virtualController = nil
// Remove virtual controller from current controllers
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
}
// MARK: - Helper Methods
private func initializeSDL() {
DispatchQueue.main.async {
setMoltenVKSettings()
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(SDL_INIT_VIDEO)
}
}
private func setupEmulation() {
virtualController?.disconnect()
controllerCallback = {
DispatchQueue.main.async {
controllersList = Ryujinx.shared.getConnectedControllers()
currentControllers.removeAll(where: { $0.name == "Apple Touch Controller" })
if controllersList.count == 2,
controllersList.contains(where: { $0.name == "Apple Touch Controller" }) {
currentControllers.append(controllersList[1])
}
print(currentControllers)
start(displayid: 1)
}
}
showVirtualController()
}
private func refreshControllersList() {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
controllersList = Ryujinx.shared.getConnectedControllers()
controllersList.removeAll(where: { $0.id == "0" })
if let game { controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
ZStack {
if let controller = controllersList.first, !controllersList.isEmpty {
} currentControllers.append(controller)
.onAppear {
start(displayid: 0)
}
} else {
HStack {
GameListView(startemu: $game)
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
controllersList = Ryujinx.shared.getConnectedControllers()
controllersList.removeAll(where: { $0.id == "0" })
}
}
List {
Section("Settings") {
NavigationLink {
SettingsView(config: $config, MoltenVKSettings: $settings)
} label: {
Text("Config")
}
}
Section("Controller") {
Button {
controllersList = Ryujinx.shared.getConnectedControllers()
controllersList.removeAll(where: { $0.id == "0" })
} label: {
Text("Refresh")
}
ForEach(controllersList, id: \.self) { controller in
HStack {
Button {
if currentControllers.contains(where: { $0.id == controller.id }) {
currentControllers.removeAll(where: { $0.id == controller.id })
} else {
currentControllers.append(controller)
}
} label: {
Text(controller.name)
}
Spacer()
if currentControllers.contains(where: { $0.id == controller.id }) {
Image(systemName: "checkmark.circle.fill")
}
}
}
}
}
}
} }
} }
} }
func start(displayid: UInt32) { private func toggleController(_ controller: Controller) {
if currentControllers.contains(where: { $0.id == controller.id }) {
if let game { currentControllers.removeAll(where: { $0.id == controller.id })
self.config.gamepath = game.path
self.config.inputids = currentControllers.map(\.id)
allocateSixGB()
// Start the emulation
print("Is MetalHud Enabled? " + (MTLHud.shared.isEnabled ? "yeah" : "nope"))
do {
setupVirtualController()
try Ryujinx.shared.start(with: config)
} catch {
print("Error \(error.localizedDescription)")
}
} else { } else {
currentControllers.append(controller)
} }
} }
func allocateSixGB() -> UnsafeMutableRawPointer? { private func start(displayid: UInt32) {
guard let game else { return }
config.gamepath = game.path
config.inputids = currentControllers.map(\.id)
allocateMemory()
do {
try Ryujinx.shared.start(with: config)
} catch {
print("Error: \(error.localizedDescription)")
}
}
private func allocateMemory() {
let physicalMemory = ProcessInfo.processInfo.physicalMemory let physicalMemory = ProcessInfo.processInfo.physicalMemory
let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024) let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024)
let mem = totalMemoryInGB
print(mem) let pointer = UnsafeMutableRawPointer.allocate(
// Allocate memory byteCount: Int(totalMemoryInGB),
let pointer = UnsafeMutableRawPointer.allocate(byteCount: Int(mem), alignment: MemoryLayout<UInt8>.alignment) alignment: MemoryLayout<UInt8>.alignment
)
// Optionally initialize the memory pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(totalMemoryInGB))
pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(mem))
print("Successfully allocated 6GB of memory.")
return pointer
} }
func patchMakeKeyAndVisible() {
let uiwindowClass = UIWindow.self
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
method_exchangeImplementations(m1, m2)
}
}
private func setMoltenVKSettings() { private func setMoltenVKSettings() {
if let configs = loadSettings() { if let configs = loadSettings() {
self.config = configs self.config = configs
print(configs)
} }
settings.forEach { setting in settings.forEach { setting in
setenv(setting.string, setting.value, 1) setenv(setting.string, setting.value, 1)
} }
} }
} }
// MARK: - Helper Functions
func loadSettings() -> Ryujinx.Configuration? { func loadSettings() -> Ryujinx.Configuration? {
guard let jsonString = UserDefaults.standard.string(forKey: "config") else { guard let jsonString = UserDefaults.standard.string(forKey: "config"),
let data = jsonString.data(using: .utf8) else {
return nil return nil
} }
do { do {
let decoder = JSONDecoder() return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
if let data = jsonString.data(using: .utf8) {
return try decoder.decode(Ryujinx.Configuration.self, from: data)
}
} catch { } catch {
print("Failed to load settings: \(error)") print("Failed to load settings: \(error)")
} return nil
return nil
}
extension UIWindow {
@objc func wdb_makeKeyAndVisible() {
print("Making window key and visible...")
self.windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
self.wdb_makeKeyAndVisible()
} }
} }

View File

@ -14,19 +14,22 @@ struct SettingsView: View {
var memoryManagerModes = [ var memoryManagerModes = [
("HostMapped", "Host (fast)"), ("HostMapped", "Host (fast)"),
("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"), ("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"),
("SoftwarePageTable", "Software") ("SoftwarePageTable", "Software (slow)"),
] ]
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false @AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack { VStack {
Section(header: Text("Graphics and Performance")) { Section(header: Title("Graphics and Performance")) {
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen) Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
Toggle("Disable V-Sync", isOn: $config.disableVSync) Toggle("Disable V-Sync", isOn: $config.disableVSync)
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache) Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression) Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
Resolution(value: $config.resscale) Resolution(value: $config.resscale)
Toggle("Enable Metal HUD", isOn: $metalHUDEnabled) Toggle("Enable Metal HUD", isOn: $metalHUDEnabled)
.onChange(of: metalHUDEnabled) { newValue in .onChange(of: metalHUDEnabled) { newValue in
@ -38,17 +41,18 @@ struct SettingsView: View {
} }
} }
Section(header: Text("Input Settings")) { Section(header: Title("Input Settings")) {
Toggle("List Input IDs", isOn: $config.listinputids) Toggle("List Input IDs", isOn: $config.listinputids)
Toggle("Nintendo Controller Layout", isOn: $config.nintendoinput)
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo)
// Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory) // Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
} }
Section(header: Text("Logging Settings")) { Section(header: Title("Logging Settings")) {
Toggle("Enable Debug Logs", isOn: $config.debuglogs) Toggle("Enable Debug Logs", isOn: $config.debuglogs)
Toggle("Enable Trace Logs", isOn: $config.tracelogs) Toggle("Enable Trace Logs", isOn: $config.tracelogs)
} }
Section(header: Text("CPU Mode")) { Section(header: Title("CPU Mode")) {
HStack { HStack {
Spacer() Spacer()
Picker("Memory Manager Mode", selection: $config.memoryManagerMode) { Picker("Memory Manager Mode", selection: $config.memoryManagerMode) {
@ -62,9 +66,11 @@ struct SettingsView: View {
Section(header: Text("Additional Settings")) { Section(header: Title("Additional Settings")) {
//TextField("Game Path", text: $config.gamepath) //TextField("Game Path", text: $config.gamepath)
Text("PageSize \(String(Int(getpagesize())))")
TextField("Additional Arguments", text: Binding( TextField("Additional Arguments", text: Binding(
get: { get: {
config.additionalArgs.joined(separator: ", ") config.additionalArgs.joined(separator: ", ")
@ -75,8 +81,8 @@ struct SettingsView: View {
)) ))
} }
} }
.padding()
} }
.padding()
.onAppear { .onAppear {
if let configs = loadSettings() { if let configs = loadSettings() {
self.config = configs self.config = configs
@ -158,3 +164,20 @@ extension NumberFormatter {
return formatter return formatter
} }
} }
struct Title: View {
let string: String
init(_ string: String) {
self.string = string
}
var body: some View {
VStack {
Text(string)
.font(.title2)
Divider()
}
}
}

View File

@ -1,3 +1,4 @@
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
@ -10,12 +11,13 @@ namespace Ryujinx.Common.Utilities
{ {
IPInterfaceProperties properties = adapter.GetIPProperties(); IPInterfaceProperties properties = adapter.GetIPProperties();
if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0)) // Skip problematic checks on non-Windows and iOS platforms
if (isPreferred || OperatingSystem.IsWindows() || properties.UnicastAddresses.Count > 0)
{ {
foreach (UnicastIPAddressInformation info in properties.UnicastAddresses) foreach (UnicastIPAddressInformation info in properties.UnicastAddresses)
{ {
// Only accept an IPv4 address // Only accept an IPv4 address
if (info.Address.GetAddressBytes().Length == 4) if (info.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{ {
return (properties, info); return (properties, info);
} }
@ -44,8 +46,9 @@ namespace Ryujinx.Common.Utilities
{ {
bool isPreferred = adapter.Id == guid; bool isPreferred = adapter.Id == guid;
// Ignore loopback and non IPv4 capable interface. // Ignore loopback and ensure the adapter supports IPv4
if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4))) if (isPreferred ||
(targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
{ {
(IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred); (IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
@ -77,7 +80,13 @@ namespace Ryujinx.Common.Utilities
public static IPAddress ConvertUint(uint ipAddress) public static IPAddress ConvertUint(uint ipAddress)
{ {
return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) }); return new IPAddress(new byte[]
{
(byte)((ipAddress >> 24) & 0xFF),
(byte)((ipAddress >> 16) & 0xFF),
(byte)((ipAddress >> 8) & 0xFF),
(byte)(ipAddress & 0xFF)
});
} }
} }
} }

View File

@ -107,35 +107,53 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
CreateFonts(uiTheme.FontFamily); CreateFonts(uiTheme.FontFamily);
} }
private void CreateFonts(string uiThemeFontFamily) private void CreateFonts(string uiThemeFontFamily)
{
// Try a list of fonts in case any of them is not available in the system.
string[] availableFonts = { uiThemeFontFamily };
// If it's iOS, we'll want to use a more appropriate set of fonts.
if (OperatingSystem.IsIOS())
{
availableFonts = new string[] {
"Chalkboard",
"Chalkboard", // San Francisco is the default font on iOS
"Chalkboard", // Legacy iOS font
"Chalkboard" // Common system font
};
}
else
{
// Fallback for other platforms (e.g., Android, Windows, etc.)
availableFonts = new string[] {
uiThemeFontFamily,
"Liberation Sans",
"FreeSans",
"DejaVu Sans",
"Lucida Grande"
};
}
// Try to create the fonts with the selected font families
foreach (string fontFamily in availableFonts)
{
try
{ {
// Try a list of fonts in case any of them is not available in the system. _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
string[] availableFonts = { return;
uiThemeFontFamily,
"Liberation Sans",
"FreeSans",
"DejaVu Sans",
"Lucida Grande",
};
foreach (string fontFamily in availableFonts)
{
try
{
_messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular);
_inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular);
_labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular);
return;
}
catch
{
}
}
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
} }
catch
{
// If the font creation fails, try the next font family
}
}
throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!");
}
private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false)
{ {

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Net;
namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types
{ {
@ -15,16 +16,23 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types
public DnsSetting(IPInterfaceProperties interfaceProperties) public DnsSetting(IPInterfaceProperties interfaceProperties)
{ {
IsDynamicDnsEnabled = OperatingSystem.IsWindows() && interfaceProperties.IsDynamicDnsEnabled; IsDynamicDnsEnabled = OperatingSystem.IsWindows() && interfaceProperties.IsDynamicDnsEnabled;
IPAddress ip = IPAddress.Parse("1.1.1.1");
if (interfaceProperties.DnsAddresses.Count == 0) if (OperatingSystem.IsIOS()) {
{ PrimaryDns = new IpV4Address(ip);
PrimaryDns = new IpV4Address(); SecondaryDns = new IpV4Address(ip);
SecondaryDns = new IpV4Address(); } else {
} if (interfaceProperties.DnsAddresses.Count == 0)
else {
{ PrimaryDns = new IpV4Address();
PrimaryDns = new IpV4Address(interfaceProperties.DnsAddresses[0]); SecondaryDns = new IpV4Address();
SecondaryDns = new IpV4Address(interfaceProperties.DnsAddresses[interfaceProperties.DnsAddresses.Count > 1 ? 1 : 0]); }
else
{
PrimaryDns = new IpV4Address(interfaceProperties.DnsAddresses[0]);
SecondaryDns = new IpV4Address(interfaceProperties.DnsAddresses[interfaceProperties.DnsAddresses.Count > 1 ? 1 : 0]);
}
} }
} }
} }

View File

@ -31,6 +31,9 @@ namespace Ryujinx.Headless.SDL2
// Input // Input
[Option("correct-ons-controller", Required = false, Default = false, HelpText = "Makes the on-screen controller (iOS) buttons correspond to what they show.")]
public bool OnScreenCorrespond { get; set; }
[Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")] [Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")]
public string InputProfile1Name { get; set; } public string InputProfile1Name { get; set; }

View File

@ -127,11 +127,17 @@ namespace Ryujinx.Headless.SDL2
}; };
} }
Parser.Default.ParseArguments<Options>(args) var result = Parser.Default.ParseArguments<Options>(args)
.WithParsed(Load) .WithParsed(options =>
.WithNotParsed(errors => errors.Output()); {
Load(options); // Load is called with the parsed options
})
.WithNotParsed(errors => errors.Output());
} }
[UnmanagedCallersOnly(EntryPoint = "get_game_controllers")] [UnmanagedCallersOnly(EntryPoint = "get_game_controllers")]
public static unsafe IntPtr GetGamepadList() public static unsafe IntPtr GetGamepadList()
{ {
@ -164,7 +170,7 @@ namespace Ryujinx.Headless.SDL2
return ptr; return ptr;
} }
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
{ {
if (inputId == null) if (inputId == null)
{ {
@ -264,8 +270,9 @@ namespace Ryujinx.Headless.SDL2
}; };
} }
else else
{ {
bool isNintendoStyle = gamepadName.Contains("Nintendo"); bool isAppleController = gamepadName.Contains("Apple") ? option.OnScreenCorrespond : false;
bool isNintendoStyle = gamepadName.Contains("Nintendo") || isAppleController;
config = new StandardControllerInputConfig config = new StandardControllerInputConfig
{ {
@ -461,9 +468,9 @@ namespace Ryujinx.Headless.SDL2
_enableKeyboard = option.EnableKeyboard; _enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse; _enableMouse = option.EnableMouse;
static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
{ {
InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index); InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index, option);
if (inputConfig != null) if (inputConfig != null)
{ {
@ -471,15 +478,15 @@ namespace Ryujinx.Headless.SDL2
} }
} }
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1); LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1, option);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2, option);
LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3); LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3, option);
LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4); LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4, option);
LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5); LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5, option);
LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6); LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6, option);
LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7); LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7, option);
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8); LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8, option);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld); LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld, option);
if (_inputConfiguration.Count == 0) if (_inputConfiguration.Count == 0)
{ {
@ -627,7 +634,7 @@ namespace Ryujinx.Headless.SDL2
options.AudioVolume, options.AudioVolume,
options.UseHypervisor ?? true, options.UseHypervisor ?? true,
options.MultiplayerLanInterfaceId, options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled); Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm);
return new Switch(configuration); return new Switch(configuration);
} }