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 */
/* 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;
attributesByRelativePath = {
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, );
@ -68,13 +68,13 @@
"Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, );
Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libSPIRV.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
Dependencies/XCFrameworks/libteakra.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, );
};
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
membershipExceptions = (
@ -86,9 +86,9 @@
Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework,
Dependencies/XCFrameworks/libavutil.xcframework,
Dependencies/XCFrameworks/libSPIRV.xcframework,
Dependencies/XCFrameworks/libswresample.xcframework,
Dependencies/XCFrameworks/libswscale.xcframework,
Dependencies/XCFrameworks/libteakra.xcframework,
Dependencies/XCFrameworks/MoltenVK.xcframework,
Dependencies/XCFrameworks/SDL2.xcframework,
);
@ -100,7 +100,7 @@
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
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;
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",
);
MARKETING_VERSION = 1.0;
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",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;

View File

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

View File

@ -23,6 +23,7 @@ class MTLHud {
static let shared = MTLHud()
private init() {
openMetalDylib()
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
enable()
} 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 debuglogs: Bool
var tracelogs: Bool
var nintendoinput: Bool
var enableInternet: Bool
var listinputids: Bool
var fullscreen: Bool
var memoryManagerMode: String
@ -64,6 +66,8 @@ class Ryujinx {
disableVSync: Bool = false,
disableShaderCache: Bool = false,
disableDockedMode: Bool = false,
nintendoinput: Bool = true,
enableInternet: Bool = false,
enableTextureRecompression: Bool = true,
additionalArgs: [String] = [],
resscale: Float = 1.00
@ -81,6 +85,8 @@ class Ryujinx {
self.additionalArgs = additionalArgs
self.memoryManagerMode = memoryManagerMode
self.resscale = resscale
self.nintendoinput = nintendoinput
self.enableInternet = enableInternet
}
}
@ -151,6 +157,13 @@ class Ryujinx {
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
if config.disableVSync {
args.append("--disable-vsync")

View File

@ -16,196 +16,230 @@ struct MoltenVKSettings: Codable, Hashable {
}
struct ContentView: View {
@State public var theWindow: UIWindow? = nil
// MARK: - Properties
@State private var theWindow: UIWindow?
@State private var virtualController: GCVirtualController?
@State var game: URL? = nil
@State var controllersList: [Controller] = []
@State var currentControllers: [Controller] = []
@State var config: Ryujinx.Configuration = Ryujinx.Configuration(gamepath: "")
@State var settings: [MoltenVKSettings] = [
// 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")
]
@State private var game: URL?
@State private var controllersList: [Controller] = []
@State private var currentControllers: [Controller] = []
@State private var config: Ryujinx.Configuration
@State private var settings: [MoltenVKSettings]
@State private var isVirtualControllerActive: Bool = false
// MARK: - Initialization
init() {
// Initialize SDL
DispatchQueue.main.async { [self] in
setMoltenVKSettings()
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(SDL_INIT_VIDEO)
patchMakeKeyAndVisible()
let defaultConfig = Ryujinx.Configuration(gamepath: "")
_config = State(initialValue: defaultConfig)
let defaultSettings: [MoltenVKSettings] = [
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")
]
_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()
configuration.elements = [
/*
GCInputLeftThumbstick,
GCInputRightThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX,
GCInputButtonY
GCInputButtonY,
*/
]
let controller = GCVirtualController(configuration: configuration)
self.virtualController = controller
self.virtualController?.connect()
virtualController = GCVirtualController(configuration: configuration)
virtualController?.connect()
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
}
var body: some View {
iOSNav {
private func destroyVirtualController() {
virtualController?.disconnect()
virtualController = nil
if let game {
ZStack {
// 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])
}
.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")
}
}
}
}
}
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" })
controllersList.removeAll(where: { $0.name == "Apple Touch Controller" })
if let controller = controllersList.first, !controllersList.isEmpty {
currentControllers.append(controller)
}
}
}
func start(displayid: UInt32) {
if let game {
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)")
}
private func toggleController(_ controller: Controller) {
if currentControllers.contains(where: { $0.id == controller.id }) {
currentControllers.removeAll(where: { $0.id == controller.id })
} 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 totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024)
let mem = totalMemoryInGB
print(mem)
// Allocate memory
let pointer = UnsafeMutableRawPointer.allocate(byteCount: Int(mem), alignment: MemoryLayout<UInt8>.alignment)
// Optionally initialize the memory
pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(mem))
print("Successfully allocated 6GB of memory.")
return pointer
let pointer = UnsafeMutableRawPointer.allocate(
byteCount: Int(totalMemoryInGB),
alignment: MemoryLayout<UInt8>.alignment
)
pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(totalMemoryInGB))
}
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() {
if let configs = loadSettings() {
self.config = configs
print(configs)
}
settings.forEach { setting in
setenv(setting.string, setting.value, 1)
}
}
}
// MARK: - Helper Functions
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
}
do {
let decoder = JSONDecoder()
if let data = jsonString.data(using: .utf8) {
return try decoder.decode(Ryujinx.Configuration.self, from: data)
}
return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
} catch {
print("Failed to load settings: \(error)")
}
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()
return nil
}
}

View File

@ -14,19 +14,22 @@ struct SettingsView: View {
var memoryManagerModes = [
("HostMapped", "Host (fast)"),
("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
var body: some View {
ScrollView {
VStack {
Section(header: Text("Graphics and Performance")) {
Section(header: Title("Graphics and Performance")) {
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen)
Toggle("Disable V-Sync", isOn: $config.disableVSync)
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache)
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression)
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode)
Resolution(value: $config.resscale)
Toggle("Enable Metal HUD", isOn: $metalHUDEnabled)
.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("Nintendo Controller Layout", isOn: $config.nintendoinput)
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo)
// 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 Trace Logs", isOn: $config.tracelogs)
}
Section(header: Text("CPU Mode")) {
Section(header: Title("CPU Mode")) {
HStack {
Spacer()
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)
Text("PageSize \(String(Int(getpagesize())))")
TextField("Additional Arguments", text: Binding(
get: {
config.additionalArgs.joined(separator: ", ")
@ -75,8 +81,8 @@ struct SettingsView: View {
))
}
}
.padding()
}
.padding()
.onAppear {
if let configs = loadSettings() {
self.config = configs
@ -158,3 +164,20 @@ extension NumberFormatter {
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.Net;
using System.Net.NetworkInformation;
@ -10,12 +11,13 @@ namespace Ryujinx.Common.Utilities
{
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)
{
// Only accept an IPv4 address
if (info.Address.GetAddressBytes().Length == 4)
if (info.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
return (properties, info);
}
@ -44,8 +46,9 @@ namespace Ryujinx.Common.Utilities
{
bool isPreferred = adapter.Id == guid;
// Ignore loopback and non IPv4 capable interface.
if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
// Ignore loopback and ensure the adapter supports IPv4
if (isPreferred ||
(targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4)))
{
(IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred);
@ -77,7 +80,13 @@ namespace Ryujinx.Common.Utilities
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);
}
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 = {
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)}!");
return;
}
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)
{

View File

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

View File

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