forked from MeloNX/MeloNX
Add On-Screen controller and Fix Internet and Lan Multiplayer
This commit is contained in:
parent
b2424a9652
commit
9a86b2000a
@ -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;
|
||||
|
Binary file not shown.
@ -55,7 +55,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
@ -23,6 +23,7 @@ class MTLHud {
|
||||
static let shared = MTLHud()
|
||||
|
||||
private init() {
|
||||
openMetalDylib()
|
||||
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||
enable()
|
||||
} else {
|
||||
|
@ -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))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
40
src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift
Normal file
40
src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user