diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 81b87add2..9b28bed53 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -75,7 +75,7 @@ namespace ARMeilleure.Translation FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); Stubs = new TranslatorStubs(FunctionTable); - FunctionTable.Fill = (ulong)Stubs.DispatchStub; + FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; } public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) diff --git a/src/MeloNX/MeloNX.xcconfig b/src/MeloNX/MeloNX.xcconfig index f8e5e88f1..42fd383c3 100644 --- a/src/MeloNX/MeloNX.xcconfig +++ b/src/MeloNX/MeloNX.xcconfig @@ -8,4 +8,4 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -VERSION = 1.7.0 +VERSION = 2.0 diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index ca1bdcc1a..f0485b609 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -32,13 +32,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 4E59B0A32DEA5CA9004BFF2A /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4E80A9852CD6F54500029585 /* Project object */; - proxyType = 1; - remoteGlobalIDString = BD43C6212D1B248D003BBC42; - remoteInfo = com.Stossy11.MeloNX.RyujinxAg; - }; 4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4E80A9852CD6F54500029585 /* Project object */; @@ -53,6 +46,13 @@ remoteGlobalIDString = 4E80A98C2CD6F54500029585; remoteInfo = MeloNX; }; + 4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4E80A9852CD6F54500029585 /* Project object */; + proxyType = 1; + remoteGlobalIDString = BD43C6212D1B248D003BBC42; + remoteInfo = com.Stossy11.MeloNX.RyujinxAg; + }; BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 4E80A9852CD6F54500029585 /* Project object */; @@ -294,7 +294,7 @@ buildRules = ( ); dependencies = ( - 4E59B0A42DEA5CA9004BFF2A /* PBXTargetDependency */, + 4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 4E80A98F2CD6F54500029585 /* MeloNX */, @@ -482,11 +482,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 4E59B0A42DEA5CA9004BFF2A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */; - targetProxy = 4E59B0A32DEA5CA9004BFF2A /* PBXContainerItemProxy */; - }; 4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4E80A98C2CD6F54500029585 /* MeloNX */; @@ -497,6 +492,11 @@ target = 4E80A98C2CD6F54500029585 /* MeloNX */; targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */; }; + 4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */; + targetProxy = 4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */; + }; BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */; @@ -647,13 +647,16 @@ isa = XCBuildConfiguration; baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; buildSettings = { + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = PixelAppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 95J8WZ4TN8; + EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; ENABLE_PREVIEWS = YES; ENABLE_TESTABILITY = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -773,6 +776,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", ); GCC_OPTIMIZATION_LEVEL = z; GENERATE_INFOPLIST_FILE = YES; @@ -1008,6 +1023,22 @@ "$(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", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = "$(VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -1025,13 +1056,16 @@ isa = XCBuildConfiguration; baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; buildSettings = { + ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = PixelAppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 95J8WZ4TN8; + EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; ENABLE_PREVIEWS = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -1151,6 +1185,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", ); GCC_OPTIMIZATION_LEVEL = z; GENERATE_INFOPLIST_FILE = YES; @@ -1386,6 +1432,22 @@ "$(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", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = "$(VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate index 0a10fc916..00f7f4a41 100644 Binary files a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate and b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h b/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h index 47eaf5f85..2ca04e253 100644 --- a/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h +++ b/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h @@ -59,6 +59,8 @@ void initialize(); int main_ryujinx_sdl(int argc, char **argv); +int update_settings_external(int argc, char **argv); + int get_current_fps(); void touch_began(float x, float y, int index); diff --git a/src/MeloNX/MeloNX/App/Core/JIT/StikJIT/StikEnableJIT.swift b/src/MeloNX/MeloNX/App/Core/JIT/StikJIT/StikEnableJIT.swift index 6e34644ea..2cde8343d 100644 --- a/src/MeloNX/MeloNX/App/Core/JIT/StikJIT/StikEnableJIT.swift +++ b/src/MeloNX/MeloNX/App/Core/JIT/StikJIT/StikEnableJIT.swift @@ -9,8 +9,6 @@ import Foundation import Network import UIKit - - func stikJITorStikDebug() -> Int { let teamid = SecTaskCopyTeamIdentifier(SecTaskCreateFromSelf(nil)!, nil) @@ -25,15 +23,28 @@ func stikJITorStikDebug() -> Int { return 0 // Not Found } +func checkforOld() -> Bool { + let teamid = SecTaskCopyTeamIdentifier(SecTaskCreateFromSelf(nil)!, nil) + + if checkifappinstalled(changeAppUI("Y29tLnN0b3NzeTExLlBvbWVsbw==") ?? "") { + return true + } + + if checkifappinstalled(changeAppUI("Y29tLnN0b3NzeTExLlBvbWVsbw==") ?? "" + ".\(String(teamid ?? ""))") { + return true + } + + if checkifappinstalled((Bundle.main.bundleIdentifier ?? "").replacingOccurrences(of: "MeloNX", with: changeAppUI("UG9tZWxv") ?? "")) { + return true + } + + return false +} + func checkifappinstalled(_ id: String) -> Bool { - guard let handle = dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_LAZY) else { - if let error = dlerror() { - print(String(cString: error)) - } return false - // fatalError("Failed to open dylib") } typealias SBSLaunchApplicationWithIdentifierFunc = @convention(c) (CFString, Bool) -> Int32 diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift index e345145d3..8836d98b6 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift @@ -356,7 +356,9 @@ class Ryujinx : ObservableObject { let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs) if result != 0 { - self.isRunning = false + DispatchQueue.main.async { + self.isRunning = false + } if let accessing, accessing { url!.stopAccessingSecurityScopedResource() } @@ -365,7 +367,9 @@ class Ryujinx : ObservableObject { } } } catch { - self.isRunning = false + DispatchQueue.main.async { + self.isRunning = false + } Thread.sleep(forTimeInterval: 0.3) let logs = LogCapture.shared.capturedLogs let parsedLogs = extractExceptionInfo(logs) @@ -384,14 +388,19 @@ class Ryujinx : ObservableObject { presentAlert(title: "MeloNX Crashed!", message: parsedLogs.exceptionType + ": " + parsedLogs.message) { - - assert(true, parsedLogs.exceptionType) + UIApplication.shared.perform(#selector(NSXPCConnection.suspend)) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + exit(0) + } } } } else { DispatchQueue.main.async { presentAlert(title: "MeloNX Crashed!", message: "Unknown Error") { - assert(true, "Exception was not detected") + UIApplication.shared.perform(#selector(NSXPCConnection.suspend)) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + exit(0) + } } } } @@ -517,7 +526,7 @@ class Ryujinx : ObservableObject { } } - private func buildCommandLineArgs(from config: Arguments) -> [String] { + func buildCommandLineArgs(from config: Arguments) -> [String] { var args: [String] = [] // Add the game path @@ -648,11 +657,10 @@ class Ryujinx : ObservableObject { // Append the input dsu servers (limit to 8 (used to be 4) just in case) if !config.inputDSUServers.isEmpty { config.inputDSUServers.prefix(8).enumerated().forEach { index, inputDSUServer in - if config.handHeldController { - args.append(contentsOf: ["\(index == 0 ? "--input-dsu-server-handheld" : "--input-dsu-server-\(index + 1)")", inputDSUServer]) - } else { - args.append(contentsOf: ["--input-dsu-server-\(index + 1)", inputDSUServer]) + if index == 0 { + args.append(contentsOf: ["--input-dsu-server-handheld", inputDSUServer]) } + args.append(contentsOf: ["--input-dsu-server-\(index + 1)", inputDSUServer]) } } diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift index 733e53dfc..e538ecac5 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift @@ -264,6 +264,7 @@ struct ABXYView: View { struct ButtonView: View { var button: VirtualControllerButton + var callback: (() -> Void)? = nil @AppStorage("onscreenhandheld") var onscreenjoy: Bool = false @AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0 @@ -344,23 +345,29 @@ struct ButtonView: View { } private func handleButtonPress() { - guard !isPressed || istoggle else { return } - - if istoggle { - toggleState.toggle() - isPressed = toggleState - let value = toggleState ? 1 : 0 - Ryujinx.shared.virtualController.setButtonState(Uint8(value), for: button) - Haptics.shared.play(.medium) + if let callback { + callback() } else { - isPressed = true - Ryujinx.shared.virtualController.setButtonState(1, for: button) - Haptics.shared.play(.medium) + guard !isPressed || istoggle else { return } + + if istoggle { + toggleState.toggle() + isPressed = toggleState + let value = toggleState ? 1 : 0 + Ryujinx.shared.virtualController.setButtonState(Uint8(value), for: button) + Haptics.shared.play(.medium) + } else { + isPressed = true + Ryujinx.shared.virtualController.setButtonState(1, for: button) + Haptics.shared.play(.medium) + } } } private func handleButtonRelease() { if istoggle { return } + + if let callback { return } guard isPressed else { return } @@ -397,40 +404,23 @@ struct ButtonView: View { // Centralized button configuration private var buttonConfig: ButtonConfiguration { switch button { - case .A: - return ButtonConfiguration(iconName: "a.circle.fill") - case .B: - return ButtonConfiguration(iconName: "b.circle.fill") - case .X: - return ButtonConfiguration(iconName: "x.circle.fill") - case .Y: - return ButtonConfiguration(iconName: "y.circle.fill") - case .leftStick: - return ButtonConfiguration(iconName: "l.joystick.press.down.fill") - case .rightStick: - return ButtonConfiguration(iconName: "r.joystick.press.down.fill") - case .dPadUp: - return ButtonConfiguration(iconName: "arrowtriangle.up.circle.fill") - case .dPadDown: - return ButtonConfiguration(iconName: "arrowtriangle.down.circle.fill") - case .dPadLeft: - return ButtonConfiguration(iconName: "arrowtriangle.left.circle.fill") - case .dPadRight: - return ButtonConfiguration(iconName: "arrowtriangle.right.circle.fill") - case .leftTrigger: - return ButtonConfiguration(iconName: "zl.rectangle.roundedtop.fill") - case .rightTrigger: - return ButtonConfiguration(iconName: "zr.rectangle.roundedtop.fill") - case .leftShoulder: - return ButtonConfiguration(iconName: "l.rectangle.roundedbottom.fill") - case .rightShoulder: - return ButtonConfiguration(iconName: "r.rectangle.roundedbottom.fill") - case .start: - return ButtonConfiguration(iconName: "plus.circle.fill") - case .back: - return ButtonConfiguration(iconName: "minus.circle.fill") - case .guide: - return ButtonConfiguration(iconName: "house.circle.fill") + case .A: return .init(iconName: "a.circle.fill") + case .B: return .init(iconName: "b.circle.fill") + case .X: return .init(iconName: "x.circle.fill") + case .Y: return .init(iconName: "y.circle.fill") + case .leftStick: return .init(iconName: "l.joystick.press.down.fill") + case .rightStick: return .init(iconName: "r.joystick.press.down.fill") + case .dPadUp: return .init(iconName: "arrowtriangle.up.circle.fill") + case .dPadDown: return .init(iconName: "arrowtriangle.down.circle.fill") + case .dPadLeft: return .init(iconName: "arrowtriangle.left.circle.fill") + case .dPadRight: return .init(iconName: "arrowtriangle.right.circle.fill") + case .leftTrigger: return .init(iconName: "zl.rectangle.roundedtop.fill") + case .rightTrigger: return .init(iconName: "zr.rectangle.roundedtop.fill") + case .leftShoulder: return .init(iconName: "l.rectangle.roundedbottom.fill") + case .rightShoulder: return .init(iconName: "r.rectangle.roundedbottom.fill") + case .start: return .init(iconName: "plus.circle.fill") + case .back: return .init(iconName: "minus.circle.fill") + case .guide: return .init(iconName: "gearshape.fill") } } @@ -438,3 +428,121 @@ struct ButtonView: View { let iconName: String } } + + +struct ExtButtonIconView: View { + var button: VirtualControllerButton + var opacity = 0.8 + + @AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0 + @State private var size: CGSize = .zero + + var body: some View { + Circle() + .foregroundStyle(.clear.opacity(0)) + .overlay { + Image(systemName: buttonConfig.iconName) + .resizable() + .scaledToFit() + .frame(width: size.width / 1.5, height: size.height / 1.5) + .foregroundStyle(.white) + .opacity(opacity) + .allowsHitTesting(false) + } + .frame(width: size.width, height: size.height) + .background( + buttonBackground + ) + .onAppear { + size = calculateButtonSize() + } + .onChange(of: controllerScale) { _ in + size = calculateButtonSize() + } + } + + private var buttonBackground: some View { + Group { + if !button.isTrigger && button != .leftStick && button != .rightStick { + Circle() + .fill(Color.gray.opacity(0.3)) + .frame(width: size.width * 1.25, height: size.height * 1.25) + } else if button == .leftStick || button == .rightStick { + Image(systemName: buttonConfig.iconName) + .resizable() + .scaledToFit() + .frame(width: size.width * 1.25, height: size.height * 1.25) + .foregroundColor(Color.gray.opacity(0.4)) + } else if button.isTrigger { + Image(systemName: convertTriggerIconToButton(buttonConfig.iconName)) + .resizable() + .scaledToFit() + .frame(width: size.width * 1.25, height: size.height * 1.25) + .foregroundColor(Color.gray.opacity(0.4)) + } + } + } + + private func convertTriggerIconToButton(_ iconName: String) -> String { + var converted = iconName + if iconName.hasPrefix("zl") || iconName.hasPrefix("zr") { + converted = String(iconName.dropFirst(3)) + } else { + converted = String(iconName.dropFirst(2)) + } + converted = converted + .replacingOccurrences(of: "rectangle", with: "button") + .replacingOccurrences(of: ".fill", with: ".horizontal.fill") + return converted + } + + private func calculateButtonSize() -> CGSize { + let baseWidth: CGFloat + let baseHeight: CGFloat + + if button.isTrigger { + baseWidth = 70 + baseHeight = 40 + } else if button.isSmall { + baseWidth = 35 + baseHeight = 35 + } else { + baseWidth = 45 + baseHeight = 45 + } + + let deviceMultiplier = UIDevice.current.userInterfaceIdiom == .pad ? 1.2 : 1.0 + let scaleMultiplier = CGFloat(controllerScale) + + return CGSize( + width: baseWidth * deviceMultiplier * scaleMultiplier, + height: baseHeight * deviceMultiplier * scaleMultiplier + ) + } + + private var buttonConfig: ButtonConfiguration { + switch button { + case .A: return .init(iconName: "a.circle.fill") + case .B: return .init(iconName: "b.circle.fill") + case .X: return .init(iconName: "x.circle.fill") + case .Y: return .init(iconName: "y.circle.fill") + case .leftStick: return .init(iconName: "l.joystick.press.down.fill") + case .rightStick: return .init(iconName: "r.joystick.press.down.fill") + case .dPadUp: return .init(iconName: "arrowtriangle.up.circle.fill") + case .dPadDown: return .init(iconName: "arrowtriangle.down.circle.fill") + case .dPadLeft: return .init(iconName: "arrowtriangle.left.circle.fill") + case .dPadRight: return .init(iconName: "arrowtriangle.right.circle.fill") + case .leftTrigger: return .init(iconName: "zl.rectangle.roundedtop.fill") + case .rightTrigger: return .init(iconName: "zr.rectangle.roundedtop.fill") + case .leftShoulder: return .init(iconName: "l.rectangle.roundedbottom.fill") + case .rightShoulder: return .init(iconName: "r.rectangle.roundedbottom.fill") + case .start: return .init(iconName: "plus.circle.fill") + case .back: return .init(iconName: "minus.circle.fill") + case .guide: return .init(iconName: "gearshape.fill") + } + } + + struct ButtonConfiguration { + let iconName: String + } +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/Joystick.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/Joystick.swift index 281d83f5f..67a7615f5 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/Joystick.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/Joystick.swift @@ -18,7 +18,7 @@ struct Joystick: View { @State private var offset: CGSize = .zero @Binding var showBackground: Bool - let sensitivity: CGFloat = 1.5 + let sensitivity: CGFloat = 1.2 var dragGesture: some Gesture { diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/JoystickView.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/JoystickView.swift index 32b791309..2459c6a7c 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/JoystickView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/Joystick/JoystickView.swift @@ -28,7 +28,6 @@ struct JoystickController: View { VStack { Joystick(position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground) .onChange(of: position) { newValue in - if iscool != nil { Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y) } else { diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift index 3118bbf52..43043a43c 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift @@ -24,6 +24,8 @@ struct EmulationView: View { @Environment(\.scenePhase) var scenePhase @State private var isInBackground = false + @State var showSettings = false + @State var pauseEmu = true @AppStorage("location-enabled") var locationenabled: Bool = false var body: some View { @@ -80,15 +82,47 @@ struct EmulationView: View { if ssb { HStack { - Image(systemName: "arrow.left.circle") - .resizable() - .frame(width: 50, height: 50) - .onTapGesture { + Menu { + + /* + Button { + showSettings.toggle() + + } label: { + Label { + Text("Game Settings") + } icon: { + Image(systemName: "gearshape.circle") + } + } + */ + + Button { + pause_emulation(pauseEmu) + pauseEmu.toggle() + } label: { + Label { + Text(pauseEmu ? "Pause" : "Play") + } icon: { + Image(systemName: pauseEmu ? "pause.circle" : "play.circle") + } + } + + Button(role: .destructive) { startgame = nil stop_emulation() try? Ryujinx.shared.stop() + } label: { + Label { + Text("Exit (Unstable)") + } icon: { + Image(systemName: "x.circle") + } } - .padding() + } label: { + ExtButtonIconView(button: .guide, opacity: 0.4) + } + .padding() Spacer() @@ -122,5 +156,11 @@ struct EmulationView: View { isInBackground = true } } + .sheet(isPresented: $showSettings) { + // PerGameSettingsView(titleId: startgame?.titleId ?? "", manager: InGameSettingsManager.shared) + // .onDisappear() { + // InGameSettingsManager.shared.saveSettings() + // } + } } } diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift new file mode 100644 index 000000000..10958d1e8 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift @@ -0,0 +1,62 @@ +// +// InGameSettingsManager.swift +// MeloNX +// +// Created by Stossy11 on 12/06/2025. +// + +import Foundation + +class InGameSettingsManager: PerGameSettingsManaging { + @Published var config: [String: Ryujinx.Arguments] + + private var saveWorkItem: DispatchWorkItem? + + public static var shared = InGameSettingsManager() + + private init() { + self.config = PerGameSettingsManager.loadSettings() ?? [:] + } + + func debouncedSave() { + saveWorkItem?.cancel() + + let workItem = DispatchWorkItem { [weak self] in + guard let self = self else { return } + self.saveSettings() + } + + saveWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: workItem) + } + + func saveSettings() { + if let currentgame = Ryujinx.shared.games.first(where: { $0.fileURL == URL(string: Ryujinx.shared.config?.gamepath ?? "") }) { + Ryujinx.shared.config = config[currentgame.titleId] + let args = Ryujinx.shared.buildCommandLineArgs(from: config[currentgame.titleId] ?? Ryujinx.Arguments()) + + // Convert Arguments to ones that Ryujinx can Read + let cArgs = args.map { strdup($0) } + defer { cArgs.forEach { free($0) } } + var argvPtrs = cArgs + + let result = update_settings_external(Int32(args.count), &argvPtrs) + + print(result) + } + } + + static func loadSettings() -> [String: Ryujinx.Arguments]? { + var cool: [String: Ryujinx.Arguments] = [:] + if let currentgame = Ryujinx.shared.games.first(where: { $0.fileURL == URL(string: Ryujinx.shared.config?.gamepath ?? "") }) { + cool[currentgame.titleId] = Ryujinx.shared.config + return cool + } else { + return nil + } + } + + func loadSettings() { + self.config = PerGameSettingsManager.loadSettings() ?? [:] + } +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift index 272ff3d51..0c5e6f02e 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift @@ -357,8 +357,16 @@ struct ContentView: View { } } + @StateObject private var persettings = PerGameSettingsManager.shared private func start(displayid: UInt32) { guard let game else { return } + var config = self.config + + persettings.loadSettings() + + if let customgame = persettings.config[game.titleId] { + config = customgame + } config.gamepath = game.fileURL.path config.inputids = Array(Set(currentControllers.map(\.id))) @@ -367,9 +375,7 @@ struct ContentView: View { registerMotionForMatchingControllers() - if config.inputids.isEmpty { - config.inputids.append("0") - } + config.inputids.isEmpty ? config.inputids.append("0") : () // Local DSU loopback to ryujinx per input id for _ in config.inputids { diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift index c2357cb8a..6125bdae9 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift @@ -26,6 +26,15 @@ struct GameLibraryView: View { @State var startgame = false @State var isSelectingGameFile = false @State var isViewingGameInfo: Bool = false + @State var gamePerGameSettings: Game? + var isShowingPerGameSettings: Binding { + Binding { + gamePerGameSettings != nil + } set: { value in + !value ? gamePerGameSettings = nil : () + } + + } @State var isSelectingGameUpdate: Bool = false @State var isSelectingGameDLC: Bool = false @StateObject var ryujinx = Ryujinx.shared @@ -201,6 +210,9 @@ struct GameLibraryView: View { .sheet(isPresented: $isSelectingGameDLC) { DLCManagerSheet(game: $gameInfo) } + .sheet(isPresented: isShowingPerGameSettings) { + PerGameSettingsView(titleId: gamePerGameSettings!.titleId) + } .sheet(isPresented: Binding( get: { isViewingGameInfo && gameInfo != nil }, set: { newValue in @@ -271,7 +283,8 @@ struct GameLibraryView: View { isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameRequirements: $gameRequirements, - gameInfo: $gameInfo + gameInfo: $gameInfo, + perGameSettings: $gamePerGameSettings ) .padding(.horizontal) .padding(.vertical, 8) @@ -288,7 +301,8 @@ struct GameLibraryView: View { isSelectingGameUpdate: $isSelectingGameUpdate, isSelectingGameDLC: $isSelectingGameDLC, gameRequirements: $gameRequirements, - gameInfo: $gameInfo + gameInfo: $gameInfo, + perGameSettings: $gamePerGameSettings ) .padding(.horizontal) .padding(.vertical, 8) @@ -482,6 +496,12 @@ struct GameLibraryView: View { } label: { Label("Game Info", systemImage: "info.circle") } + + Button { + gamePerGameSettings = game + } label: { + Label("\(game.titleName) Settings", systemImage: "gear") + } } Section { @@ -501,6 +521,12 @@ struct GameLibraryView: View { } Section { + Button(role: .destructive) { + removeFromRecentGames(game) + } label: { + Label("Remove from Recents", systemImage: "trash") + } + if #available(iOS 15, *) { Button(role: .destructive) { deleteGame(game: game) @@ -771,6 +797,8 @@ struct GameListRow: View { @Binding var isSelectingGameDLC: Bool @Binding var gameRequirements: [GameRequirements] @Binding var gameInfo: Game? + @StateObject private var settingsManager = PerGameSettingsManager.shared + @Binding var perGameSettings: Game? @State var gametoDelete: Game? @State var showGameDeleteConfirmation: Bool = false @Environment(\.colorScheme) var colorScheme @@ -828,6 +856,14 @@ struct GameListRow: View { } } + if $settingsManager.config.wrappedValue.contains(where: { $0.key == game.titleId }) { + Image(systemName: "gearshape.circle") + .resizable() + .aspectRatio(contentMode: .fill) + .foregroundStyle(.blue) + .frame(width: 20, height: 20) + } + Spacer() VStack(alignment: .leading) { @@ -897,6 +933,12 @@ struct GameListRow: View { } label: { Label("Game Info", systemImage: "info.circle") } + + Button { + perGameSettings = game + } label: { + Label("\(game.titleName) Settings", systemImage: "gear") + } } Section { @@ -959,10 +1001,7 @@ struct GameListRow: View { Text("Are you sure you want to delete \(gametoDelete?.titleName ?? "this game")?") } .listRowInsets(EdgeInsets()) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5)) - ) + .wow(colorScheme) } else { Button(action: { startemu = game @@ -1196,3 +1235,20 @@ func pullGameCompatibility(completion: @escaping (Result<[GameRequirements], Err task.resume() } + +extension View { + func wow(_ colorScheme: ColorScheme) -> some View { + if #available(iOS 26.0, *) { + return self + .glassEffect(Glass.regular, in: + RoundedRectangle(cornerRadius: 12) + ) + } else { + return self + .background( + RoundedRectangle(cornerRadius: 12) + .fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5)) + ) + } + } +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/AppIcon/AppIconSwitcher.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/AppIcon/AppIconSwitcher.swift new file mode 100644 index 000000000..aeea14df4 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/AppIcon/AppIconSwitcher.swift @@ -0,0 +1,236 @@ +// +// AppIconSwitcher.swift +// MeloNX +// +// Created by Stossy11 on 02/06/2025. +// + +import SwiftUI + +struct AppIcon: Identifiable, Equatable { + var id: String { creator } + + var iconNames: [String: String] + var creator: String +} + +struct AppIconSwitcherView: View { + @Environment(\.dismiss) private var dismiss + @State var appIcons: [AppIcon] = [ + AppIcon(iconNames: ["Default": UIImage.appIcon(), "Dark Mode": "DarkMode", "Round": "RoundAppIcon"], creator: "CycloKid"), + AppIcon(iconNames: ["Pixel Default": "PixelAppIcon", "Pixel Round": "PixelRoundAppIcon"], creator: "Nobody"), + AppIcon(iconNames: ["\"UwU\"": "uwuAppIcon"], creator: "𝒰𝓃𝓀𝓃𝑜𝓌𝓃") + + ] + + @State var columns: [GridItem] = [ + GridItem(.flexible(), spacing: 20), + GridItem(.flexible(), spacing: 20), + GridItem(.flexible(), spacing: 20) + ] + @State private var currentIconName: String? = nil + @State var refresh = 0 + + var body: some View { + NavigationView { + ZStack { + LinearGradient( + gradient: Gradient(colors: [ + Color(.systemBackground).opacity(0.95), + Color(.systemGroupedBackground) + ]), + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea() + + ScrollView { + LazyVStack(spacing: 32) { + ForEach(appIcons.indices, id: \.self) { index in + let iconGroup = appIcons[index] + + VStack(alignment: .leading, spacing: 20) { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(iconGroup.creator) + .font(.title2) + .fontWeight(.bold) + .foregroundStyle(.primary) + + Text("\(iconGroup.iconNames.count) icons") + .font(.caption) + .foregroundStyle(.secondary) + } + Spacer() + } + .padding(.horizontal, 24) + + LazyVGrid(columns: columns, spacing: 20) { + ForEach(Array(iconGroup.iconNames.keys.sorted()), id: \.self) { key in + if let iconName = iconGroup.iconNames[key] { + Button { + selectIcon(iconName) + } label: { + ZStack { + AppIconView(app: (iconName, key)) + + if iconName == currentIconName ?? UIImage.appIcon() { + VStack { + HStack { + Spacer() + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 24, weight: .bold)) + .foregroundStyle(.white) + .background( + Circle() + .fill( + LinearGradient( + colors: [.blue, .purple], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: 28, height: 28) + ) + } + Spacer() + } + .frame(width: 80, height: 80) + .offset(x: 6, y: -6) + } + } + } + .buttonStyle(PlainButtonStyle()) + .scaleEffect(isCurrentIcon(iconName) ? 0.95 : 1.0) + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isCurrentIcon(iconName)) + } + } + } + .padding(.horizontal, 24) + } + + // Stylized divider + if index < appIcons.count - 1 { + Rectangle() + .fill( + LinearGradient( + colors: [.clear, Color(.separator), .clear], + startPoint: .leading, + endPoint: .trailing + ) + ) + .frame(height: 1) + .padding(.horizontal, 40) + } + } + } + .padding(.vertical, 32) + } + } + .navigationTitle("Choose App Icon") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(.blue) + } + } + } + .onAppear(perform: setupColumns) + .onAppear(perform: getCurrentIconName) + } + + private func setupColumns() { + if #available(iOS 18.5, *) { + // + } else { + if checkforOld() { + if let value = appIcons[0].iconNames.removeValue(forKey: "Round") { + appIcons[0].iconNames["PomeloNX"] = value + } + + if let value = appIcons[1].iconNames.removeValue(forKey: "Pixel Round") { + appIcons[1].iconNames["Pixel PomeloNX"] = value + } + } + } + } + + private func getCurrentIconName() { + currentIconName = UIApplication.shared.alternateIconName ?? UIImage.appIcon() + } + + private func isCurrentIcon(_ iconName: String) -> Bool { + let currentIcon = UIApplication.shared.alternateIconName ?? UIImage.appIcon() + return currentIcon == iconName + } + + private func selectIcon(_ iconName: String) { + // Haptic feedback + let impactFeedback = UIImpactFeedbackGenerator(style: .medium) + impactFeedback.impactOccurred() + + if iconName == UIImage.appIcon() { + UIApplication.shared.setAlternateIconName(nil) { error in + if let error = error { + print("Error setting icon: \(error)") + } else { + DispatchQueue.main.async { + currentIconName = nil + refresh = Int.random(in: 0...100) + } + } + } + } else { + UIApplication.shared.setAlternateIconName(iconName) { error in + if let error = error { + print("Error setting icon: \(error)") + } else { + DispatchQueue.main.async { + currentIconName = iconName + refresh = Int.random(in: 0...100) + } + } + } + } + } +} + +struct AppIconView: View { + let app: (String, String) + + var body: some View { + VStack(spacing: 7) { + ZStack { + Image(uiImage: UIImage(named: app.0)!) + .resizable() + .cornerRadius(15) + .frame(width: 62, height: 62) + .shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 1) + } + + Text(app.1) + .font(.system(size: 12, weight: .medium)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 1) + .frame(width: 100) + .lineLimit(1) + } + } +} + +extension UIImage { + static func appIcon() -> String { + if let icons = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any], + let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], + let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], + let lastIcon = iconFiles.last { + return lastIcon + } + return "AppIcon" + } +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/Per-Game Settings/PerGameSettingsView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/Per-Game Settings/PerGameSettingsView.swift new file mode 100644 index 000000000..c53c8a99a --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/Per-Game Settings/PerGameSettingsView.swift @@ -0,0 +1,707 @@ +// +// PerGameSettingsView.swift +// MeloNX +// +// Created by Stossy11 on 12/06/2025. +// + +import SwiftUI + +protocol PerGameSettingsManaging: ObservableObject { + var config: [String: Ryujinx.Arguments] { get set } + + func debouncedSave() + func saveSettings() + func loadSettings() + + static func loadSettings() -> [String: Ryujinx.Arguments]? +} + + + +class PerGameSettingsManager: PerGameSettingsManaging { + @Published var config: [String: Ryujinx.Arguments] { + didSet { + debouncedSave() + } + } + + private var saveWorkItem: DispatchWorkItem? + + public static var shared = PerGameSettingsManager() + + private init() { + self.config = PerGameSettingsManager.loadSettings() ?? [:] + } + + func debouncedSave() { + saveWorkItem?.cancel() + + let workItem = DispatchWorkItem { [weak self] in + guard let self = self else { return } + self.saveSettings() + } + + saveWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: workItem) + } + + func saveSettings() { + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(config) + + let fileURL = URL.documentsDirectory.appendingPathComponent("config-pergame.json") + + try data.write(to: fileURL) + print("Settings saved successfully") + } catch { + print("Failed to save settings: \(error)") + } + } + + static func loadSettings() -> [String: Ryujinx.Arguments]? { + do { + let fileURL = URL.documentsDirectory.appendingPathComponent("config-pergame.json") + + guard FileManager.default.fileExists(atPath: fileURL.path) else { + print("Config file does not exist, creating new config") + return nil + } + + let data = try Data(contentsOf: fileURL) + + let decoder = JSONDecoder() + let configs = try decoder.decode([String: Ryujinx.Arguments].self, from: data) + return configs + } catch { + print("Failed to load settings: \(error)") + return nil + } + } + + func loadSettings() { + self.config = PerGameSettingsManager.loadSettings() ?? [:] + } +} + + +struct PerGameSettingsView: View { + + @StateObject private var settingsManager: PerGameSettingsManager + + var titleId: String + + init(titleId: String, manager: any PerGameSettingsManaging = PerGameSettingsManager.shared) { + self._settingsManager = StateObject(wrappedValue: manager as! PerGameSettingsManager) + self.titleId = titleId + } + + + private var config: Binding { + return Binding { + return settingsManager.config[titleId] ?? Ryujinx.Arguments() + } set: { newValue in + settingsManager.config[titleId] = newValue + settingsManager.debouncedSave() + } + } + + var memoryManagerModes = [ + ("HostMapped", "Host (fast)"), + ("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"), + ("SoftwarePageTable", "Software (slow)"), + ] + + + let totalMemory = ProcessInfo.processInfo.physicalMemory + + @State private var showResolutionInfo = false + @State private var showAnisotropicInfo = false + @State private var showControllerInfo = false + @State private var showAppIconSwitcher = false + @State private var searchText = "" + @StateObject var ryujinx = Ryujinx.shared + @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) var colorScheme + @Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass? + @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? + + @State private var selectedCategory: PerSettingsCategory = .graphics + + @StateObject var metalHudEnabler = MTLHud.shared + + var filteredMemoryModes: [(String, String)] { + guard !searchText.isEmpty else { return memoryManagerModes } + return memoryManagerModes.filter { $0.1.localizedCaseInsensitiveContains(searchText) } + } + + var appVersion: String { + guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { + return "Unknown" + } + return version + } + + @FocusState private var isArgumentsKeyboardVisible: Bool + + + @State private var selectedView = "Data Management" + @State private var sidebar = true + + enum PerSettingsCategory: String, CaseIterable, Identifiable { + case graphics = "Graphics" + case system = "System" + case advanced = "Advanced" + + var id: String { self.rawValue } + + var icon: String { + switch self { + case .graphics: return "paintbrush.fill" + case .system: return "gearshape.fill" + case .advanced: return "terminal.fill" + } + } + } + + var body: some View { + iOSNav { + ZStack { + Color(UIColor.systemBackground) + .ignoresSafeArea() + + VStack(spacing: 0) { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 12) { + ForEach(PerSettingsCategory.allCases, id: \.id) { category in + CategoryButton( + title: category.rawValue, + icon: category.icon, + isSelected: selectedCategory == category + ) { + selectedCategory = category + } + } + } + .padding(.horizontal) + .padding(.vertical, 8) + } + + Divider() + + // Settings content + ScrollView { + VStack(spacing: 24) { + switch selectedCategory { + case .graphics: + graphicsSettings + .padding(.top) + case .system: + systemSettings + .padding(.top) + case .advanced: + advancedSettings + .padding(.top) + + } + + Spacer(minLength: 50) + } + .padding(.bottom) + } + .scrollDismissesKeyboardIfAvailable() + } + } + .navigationTitle("Settings") + .navigationBarTitleDisplayMode(.large) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { + settingsManager.debouncedSave() + dismiss() + } + } + + ToolbarItem(placement: .cancellationAction) { + Button("Reset") { + dismiss() + settingsManager.config[titleId] = nil + settingsManager.saveSettings() + } + } + } + // .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic)) + .onAppear { + + // if let configs = SettingsManager.loadSettings() { + // settingsManager.loadSettings() + // } else { + // settingsManager.saveSettings() + //} + + print(titleId) + + if settingsManager.config[titleId] == nil { + settingsManager.config[titleId] = Ryujinx.Arguments() + settingsManager.debouncedSave() + } + } + } + } + + // MARK: - Graphics Settings + + private var graphicsSettings: some View { + SettingsSection(title: "Graphics & Performance") { + // Resolution scale card + SettingsCard { + VStack(alignment: .leading, spacing: 12) { + HStack { + labelWithIcon("Resolution Scale", iconName: "magnifyingglass") + .font(.headline) + Spacer() + Button { + showResolutionInfo.toggle() + } label: { + Image(systemName: "info.circle") + .foregroundColor(.secondary) + } + .buttonStyle(.plain) + .alert(isPresented: $showResolutionInfo) { + Alert( + title: Text("Resolution Scale"), + message: Text("Adjust the internal rendering resolution. Higher values improve visuals but may reduce performance."), + dismissButton: .default(Text("OK")) + ) + } + } + + VStack(spacing: 8) { + Slider(value: config.resscale, in: 0.1...3.0, step: 0.05) + + HStack { + Text("0.1x") + .font(.caption2) + .foregroundColor(.secondary) + + Spacer() + + Text("\(config.resscale.wrappedValue, specifier: "%.2f")x") + .font(.headline) + .foregroundColor(.blue) + + Spacer() + + Text("3.0x") + .font(.caption2) + .foregroundColor(.secondary) + } + } + } + } + + // Anisotropic filtering card + SettingsCard { + VStack(alignment: .leading, spacing: 12) { + HStack { + labelWithIcon("Max Anisotropic Filtering", iconName: "magnifyingglass") + .font(.headline) + Spacer() + Button { + showAnisotropicInfo.toggle() + } label: { + Image(systemName: "info.circle") + .foregroundColor(.secondary) + } + .buttonStyle(.plain) + .alert(isPresented: $showAnisotropicInfo) { + Alert( + title: Text("Max Anisotropic Filtering"), + message: Text("Adjust the internal Anisotropic filtering. Higher values improve texture quality at angles but may reduce performance. Default at 0 lets game decide."), + dismissButton: .default(Text("OK")) + ) + } + } + + VStack(spacing: 8) { + Slider(value: config.maxAnisotropy, in: 0...16.0, step: 0.1) + + HStack { + Text("Off") + .font(.caption2) + .foregroundColor(.secondary) + + Spacer() + + Text("\(config.maxAnisotropy.wrappedValue, specifier: "%.1f")x") + .font(.headline) + .foregroundColor(.blue) + + Spacer() + + Text("16x") + .font(.caption2) + .foregroundColor(.secondary) + } + } + } + } + + // Toggle options card + SettingsCard { + VStack(spacing: 4) { + PerSettingsToggle(isOn: config.disableShaderCache, icon: "memorychip", label: "Shader Cache") + + Divider() + + PerSettingsToggle(isOn: config.disablevsync, icon: "arrow.triangle.2.circlepath", label: "Disable VSync") + + Divider() + + PerSettingsToggle(isOn: config.enableTextureRecompression, icon: "rectangle.compress.vertical", label: "Texture Recompression") + + Divider() + + PerSettingsToggle(isOn: config.disableDockedMode, icon: "dock.rectangle", label: "Docked Mode") + + Divider() + + PerSettingsToggle(isOn: config.macroHLE, icon: "gearshape", label: "Macro HLE") + } + } + + // Aspect ratio card + SettingsCard { + VStack(alignment: .leading, spacing: 12) { + labelWithIcon("Aspect Ratio", iconName: "rectangle.expand.vertical") + .font(.headline) + + if (horizontalSizeClass == .regular && verticalSizeClass == .regular) || (horizontalSizeClass == .regular && verticalSizeClass == .compact) { + Picker(selection: config.aspectRatio) { + ForEach(AspectRatio.allCases, id: \.self) { ratio in + Text(ratio.displayName).tag(ratio) + } + } label: { + EmptyView() + } + .pickerStyle(.segmented) + } else { + Picker(selection: config.aspectRatio) { + ForEach(AspectRatio.allCases, id: \.self) { ratio in + Text(ratio.displayName).tag(ratio) + } + } label: { + EmptyView() + } + .pickerStyle(.menu) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 4) + } + } + } + } + } + + + // MARK: - System Settings + + private var systemSettings: some View { + SettingsSection(title: "System Configuration") { + // Language and region card + SettingsCard { + VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 8) { + labelWithIcon("System Language", iconName: "character.bubble") + .font(.headline) + + Picker(selection: config.language) { + ForEach(SystemLanguage.allCases, id: \.self) { language in + Text(language.displayName).tag(language) + } + } label: { + EmptyView() + } + .pickerStyle(.menu) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 4) + } + + Divider() + + VStack(alignment: .leading, spacing: 8) { + labelWithIcon("Region", iconName: "globe") + .font(.headline) + + Picker(selection: config.regioncode) { + ForEach(SystemRegionCode.allCases, id: \.self) { region in + Text(region.displayName).tag(region) + } + } label: { + EmptyView() + } + .pickerStyle(.menu) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 4) + } + } + } + + // CPU options card + SettingsCard { + VStack(alignment: .leading, spacing: 16) { + Text("CPU Configuration") + .font(.headline) + .foregroundColor(.primary) + + VStack(alignment: .leading, spacing: 8) { + Text("Memory Manager Mode") + .font(.subheadline) + .foregroundColor(.secondary) + + Picker(selection: config.memoryManagerMode) { + ForEach(filteredMemoryModes, id: \.0) { key, displayName in + Text(displayName).tag(key) + } + } label: { + EmptyView() + } + .pickerStyle(.segmented) + } + + Divider() + + PerSettingsToggle(isOn: config.disablePTC, icon: "cpu", label: "Disable PTC") + + if let gpuInfo = getGPUInfo(), gpuInfo.hasPrefix("Apple M") { + Divider() + + if #available(iOS 16.4, *) { + PerSettingsToggle(isOn: .constant(false), icon: "bolt", label: "Hypervisor") + .disabled(true) + } else if checkAppEntitlement("com.apple.private.hypervisor") { + PerSettingsToggle(isOn: config.hypervisor, icon: "bolt", label: "Hypervisor") + } + } + } + } + + // Controller options card + SettingsCard { + VStack(alignment: .leading, spacing: 16) { + Text("Controller Configuration") + .font(.headline) + .foregroundColor(.primary) + + PerSettingsToggle(isOn: config.handHeldController, icon: "formfitting.gamecontroller", label: "Player 1 to Handheld") + + } + } + } + } + + // MARK: - Advanced Settings + + private var advancedSettings: some View { + SettingsSection(title: "Advanced Options") { + // Debug options card + SettingsCard { + VStack(spacing: 4) { + PerSettingsToggle(isOn: config.debuglogs, icon: "exclamationmark.bubble", label: "Debug Logs") + + Divider() + + PerSettingsToggle(isOn: config.tracelogs, icon: "waveform.path", label: "Trace Logs") + } + } + + // Advanced toggles card + SettingsCard { + VStack(spacing: 4) { + + PerSettingsToggle(isOn: config.dfsIntegrityChecks, icon: "checkmark.shield", label: "Disable FS Integrity Checks") + .accentColor(.red) + + Divider() + + PerSettingsToggle(isOn: config.expandRam, icon: "exclamationmark.bubble", label: "Expand Guest RAM") + .accentColor(.red) + .disabled(totalMemory < 5723) + + Divider() + + PerSettingsToggle(isOn: config.ignoreMissingServices, icon: "waveform.path", label: "Ignore Missing Services") + .accentColor(.red) + } + } + + // Additional args card + SettingsCard { + VStack(alignment: .leading, spacing: 12) { + Text("Additional Arguments") + .font(.headline) + .foregroundColor(.primary) + + let binding = Binding( + get: { + config.additionalArgs.wrappedValue.joined(separator: ", ") + }, + set: { newValue in + let args = newValue + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespaces) } + config.additionalArgs.wrappedValue = args + } + ) + + + if #available(iOS 15.0, *) { + TextField("Separate arguments with commas", text: binding) + .font(.system(.body, design: .monospaced)) + .textFieldStyle(.roundedBorder) + .textInputAutocapitalization(.none) + .disableAutocorrection(true) + .padding(.vertical, 4) + .toolbar { + ToolbarItem(placement: .keyboard) { + Button("Dismiss") { + isArgumentsKeyboardVisible = false + } + } + } + .focused($isArgumentsKeyboardVisible) + } else { + TextField("Separate arguments with commas", text: binding) + .font(.system(.body, design: .monospaced)) + .textFieldStyle(.roundedBorder) + .disableAutocorrection(true) + .padding(.vertical, 4) + } + } + } + + // Page size info card + SettingsCard { + HStack { + labelWithIcon("Page Size", iconName: "textformat.size") + Spacer() + Text("\(String(Int(getpagesize())))") + .font(.system(.body, design: .monospaced)) + .foregroundColor(.secondary) + } + } + } + } + + // MARK: - Miscellaneous Settings + + private var miscSettings: some View { + SettingsSection(title: "Miscellaneous Options") { + SettingsCard { + VStack(spacing: 4) { + PerSettingsToggle(isOn: config.handHeldController, icon: "formfitting.gamecontroller", label: "Player 1 to Handheld") + } + } + } + } + + // MARK: - Helper Functions + + + func getGPUInfo() -> String? { + let device = MTLCreateSystemDefaultDevice() + return device?.name + } + + @ViewBuilder + private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View { + HStack(spacing: 8) { + if iconName.hasSuffix(".svg") { + if let flipimage, flipimage { + SVGView(svgName: iconName, color: .blue) + // .symbolRenderingMode(.hierarchical) + .frame(width: 20, height: 20) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } else { + SVGView(svgName: iconName, color: .blue) + // .symbolRenderingMode(.hierarchical) + .frame(width: 20, height: 20) + } + } else if !iconName.isEmpty { + Image(systemName: iconName) + // .symbolRenderingMode(.hierarchical) + .foregroundColor(.blue) + } + Text(text) + } + .font(.body) + } +} + + +// MARK: - Supporting Views + +// PerSettingsToggle(isOn: config.handHeldController, icon: "formfitting.gamecontroller", label: "Player 1 to Handheld") + +struct PerSettingsCard: View { + @Environment(\.colorScheme) var colorScheme + @AppStorage("oldSettingsUI") var oldSettingsUI = false + let content: Content + + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + var body: some View { + content + .padding() + .background( + RoundedRectangle(cornerRadius: 12) + .fill(colorScheme == .dark ? Color(.systemGray6) : Color.white) + .shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 2) + ) + .padding(.horizontal) + } +} + +struct PerSettingsToggle: View { + @Binding var isOn: Bool + let icon: String + let label: String + var disabled: Bool = false + @AppStorage("toggleGreen") var toggleGreen: Bool = false + @AppStorage("oldSettingsUI") var oldSettingsUI = false + + var body: some View { + Toggle(isOn: $isOn) { + HStack(spacing: 8) { + if icon.hasSuffix(".svg") { + SVGView(svgName: icon, color: .blue) + .frame(width: 20, height: 20) + } else { + Image(systemName: icon) + // .symbolRenderingMode(.hierarchical) + .foregroundColor(.blue) + } + + Text(label) + .font(.body) + } + } + .toggleStyle(SwitchToggleStyle(tint: .blue)) + .disabled(disabled) + .padding(.vertical, 6) + } + + func disabled(_ disabled: Bool) -> PerSettingsToggle { + var view = self + view.disabled = disabled + return view + } + + func accentColor(_ color: Color) -> PerSettingsToggle { + var view = self + return view + } +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift index 4d2844503..d8f4a91c4 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift @@ -266,6 +266,8 @@ struct SettingsViewNew: View { @AppStorage("runOnMainThread") var runOnMainThread = false + @AppStorage("oldSettingsUI") var oldSettingsUI = false + @AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState() let totalMemory = ProcessInfo.processInfo.physicalMemory @@ -275,6 +277,7 @@ struct SettingsViewNew: View { @State private var showResolutionInfo = false @State private var showAnisotropicInfo = false @State private var showControllerInfo = false + @State private var showAppIconSwitcher = false @State private var searchText = "" @AppStorage("portal") var gamepo = false @StateObject var ryujinx = Ryujinx.shared @@ -327,10 +330,12 @@ struct SettingsViewNew: View { var body: some View { if UIDevice.current.userInterfaceIdiom == .phone { iOSSettings - } else { + } else if !oldSettingsUI { iPadOSSettings .ignoresSafeArea() .edgesIgnoringSafeArea(.all) + } else { + iOSSettings } } @@ -1188,7 +1193,6 @@ struct SettingsViewNew: View { if #available(iOS 15.0, *) { - TextField("Separate arguments with commas", text: binding) .font(.system(.body, design: .monospaced)) .textFieldStyle(.roundedBorder) @@ -1254,13 +1258,31 @@ struct SettingsViewNew: View { Divider() if colorScheme == .light { - SettingsToggle(isOn: $disableTouch, icon: "iphone.slash", label: "Black Screen when using AirPlay") + SettingsToggle(isOn: $blackScreen, icon: "iphone.slash", label: "Black Screen when using AirPlay") Divider() } + Button { + showAppIconSwitcher = true + } label: { + HStack { + Image(systemName: "app.dashed") + .foregroundColor(.blue) + Text("App Icon Switcher") + .foregroundColor(.primary) + Spacer() + } + .padding(.vertical, 8) + } + .sheet(isPresented: $showAppIconSwitcher) { + AppIconSwitcherView() + } + + Divider() + // Exit button card - SettingsToggle(isOn: $ssb, icon: "arrow.left.circle", label: "Exit Button") + SettingsToggle(isOn: $ssb, icon: "arrow.left.circle", label: "Menu Button (in-game)") Divider() @@ -1275,6 +1297,13 @@ struct SettingsViewNew: View { Divider() + if UIDevice.current.userInterfaceIdiom == .pad { + // Old Settings UI + SettingsToggle(isOn: $oldSettingsUI, icon: "ipad.landscape", label: "Non Switch-like Settings") + + Divider() + } + // JIT options if #available(iOS 17.0.1, *) { @@ -1401,8 +1430,6 @@ struct SVGView: UIViewRepresentable { svgName.removeLast(4) } - - _ = UIView(svgNamed: svgName) { svgLayer in svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color svgLayer.resizeToFit(hammock.frame) @@ -1505,6 +1532,7 @@ struct SettingsSection: View { struct SettingsCard: View { @Environment(\.colorScheme) var colorScheme + @AppStorage("oldSettingsUI") var oldSettingsUI = false let content: Content init(@ViewBuilder content: () -> Content) { @@ -1512,7 +1540,7 @@ struct SettingsCard: View { } var body: some View { - if UIDevice.current.userInterfaceIdiom == .phone { + if UIDevice.current.userInterfaceIdiom == .phone || oldSettingsUI { content .padding() .background( @@ -1538,9 +1566,10 @@ struct SettingsToggle: View { let label: String var disabled: Bool = false @AppStorage("toggleGreen") var toggleGreen: Bool = false + @AppStorage("oldSettingsUI") var oldSettingsUI = false var body: some View { - if UIDevice.current.userInterfaceIdiom == .phone { + if UIDevice.current.userInterfaceIdiom == .phone || oldSettingsUI { Toggle(isOn: $isOn) { HStack(spacing: 8) { if icon.hasSuffix(".svg") { diff --git a/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift b/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift index a869f5e8c..aeb7e5146 100644 --- a/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift +++ b/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift @@ -40,40 +40,61 @@ struct MeloNXApp: App { @AppStorage("autoJIT") var autoJIT = false + @State var fourgbiPad = false + @AppStorage("4GB iPad") var ignores = false + // String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000) var body: some Scene { WindowGroup { - if finishedStorage { - ContentView() - .withFileImporter() - .onAppear { - if checkForUpdate { - checkLatestVersion() + Group { + if finishedStorage { + ContentView() + .withFileImporter() + .onAppear { + if checkForUpdate { + checkLatestVersion() + } + + print(metalHudEnabler.canMetalHud) + + UserDefaults.standard.set(false, forKey: "lockInApp") } - - print(metalHudEnabler.canMetalHud) - - UserDefaults.standard.set(false, forKey: "lockInApp") - } - .sheet(isPresented: Binding( - get: { showOutOfDateSheet && updateInfo != nil }, - set: { newValue in - if !newValue { - showOutOfDateSheet = false - updateInfo = nil + .sheet(isPresented: Binding( + get: { showOutOfDateSheet && updateInfo != nil }, + set: { newValue in + if !newValue { + showOutOfDateSheet = false + updateInfo = nil + } + } + )) { + if let updateInfo = updateInfo { + MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet) } } - )) { - if let updateInfo = updateInfo { - MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet) - } - } - } else { - SetupView(finished: $finished) - .onChange(of: finished) { newValue in - withAnimation(.easeOut) { - finishedStorage = newValue + } else { + SetupView(finished: $finished) + .onChange(of: finished) { newValue in + withAnimation(.easeOut) { + finishedStorage = newValue + } } + } + } + .onAppear() { + if UIDevice.current.userInterfaceIdiom == .pad && !ignores { + print((Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000)) + if round(Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000) <= 4 { + fourgbiPad = true } + } + } + .alert("Unsupported Device", isPresented: $fourgbiPad) { + Button("Continue") { + ignores = true + fourgbiPad = false + } + } message: { + Text("Your Device is an iPad with \(String(format: "%.0f GB", Double(ProcessInfo.processInfo.physicalMemory) / 1_000_000_000)) of memory, MeloNX has issues with those devices") } } } @@ -122,3 +143,8 @@ struct MeloNXApp: App { task.resume() } } + +func changeAppUI(_ string: String) -> String? { + guard let data = Data(base64Encoded: string) else { return nil } + return String(data: data, encoding: .utf8) +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.appiconset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.appiconset/Contents.json new file mode 100644 index 000000000..5607ee502 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "darker.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.appiconset/darker.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.appiconset/darker.png new file mode 100644 index 000000000..e4096e734 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.appiconset/darker.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/Contents.json new file mode 100644 index 000000000..fb81560ef --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "darker.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/darker.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/darker.png new file mode 100644 index 000000000..e4096e734 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/darker.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.appiconset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.appiconset/Contents.json new file mode 100644 index 000000000..739e96301 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "MeloNX 1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.appiconset/MeloNX 1024.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.appiconset/MeloNX 1024.png new file mode 100644 index 000000000..14245da09 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.appiconset/MeloNX 1024.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/Contents.json new file mode 100644 index 000000000..729a76a41 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "MeloNX 1024.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/MeloNX 1024.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/MeloNX 1024.png new file mode 100644 index 000000000..14245da09 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/MeloNX 1024.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.appiconset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.appiconset/Contents.json new file mode 100644 index 000000000..f46306ca5 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "PixelPomeloNX 1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.appiconset/PixelPomeloNX 1024.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.appiconset/PixelPomeloNX 1024.png new file mode 100644 index 000000000..9aa22bdc6 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.appiconset/PixelPomeloNX 1024.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/Contents.json new file mode 100644 index 000000000..77faa36d9 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "PixelPomeloNX 1024.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/PixelPomeloNX 1024.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/PixelPomeloNX 1024.png new file mode 100644 index 000000000..9aa22bdc6 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/PixelPomeloNX 1024.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.appiconset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.appiconset/Contents.json new file mode 100644 index 000000000..c843eafa5 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "copycat.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.appiconset/copycat.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.appiconset/copycat.png new file mode 100644 index 000000000..034a52652 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.appiconset/copycat.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/Contents.json new file mode 100644 index 000000000..a16ce9fc7 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "copycat.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/copycat.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/copycat.png new file mode 100644 index 000000000..034a52652 Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/copycat.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.appiconset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.appiconset/Contents.json new file mode 100644 index 000000000..37268d676 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "melowonx.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.appiconset/melowonx.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.appiconset/melowonx.png new file mode 100644 index 000000000..ad0867e2f Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.appiconset/melowonx.png differ diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/Contents.json b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/Contents.json new file mode 100644 index 000000000..52e9bd731 --- /dev/null +++ b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "melowonx.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/melowonx.png b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/melowonx.png new file mode 100644 index 000000000..ad0867e2f Binary files /dev/null and b/src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/melowonx.png differ diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist index 0a5037678..6b69c67ad 100644 Binary files a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist and b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist differ diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper index cfb7b2348..b89e9d5b0 100755 Binary files a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper and b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper differ diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources index 8ff39bf10..0483b32cc 100644 --- a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources +++ b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources @@ -10,7 +10,7 @@ Info.plist - UOH9NuuEcz5NQiQlrM2LNFaG2pI= + RTwvCLsTMs+YfZ9ZeF25QYe7/LE= Modules/module.modulemap diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/StosJIT b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/StosJIT index 9bc158566..4b24b750d 100755 Binary files a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/StosJIT and b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/StosJIT differ diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/_CodeSignature/CodeResources b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/_CodeSignature/CodeResources index 0b83293b3..8cc62f1fe 100644 --- a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/_CodeSignature/CodeResources +++ b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/StosJIT.framework/_CodeSignature/CodeResources @@ -4,6 +4,10 @@ files + .DS_Store + + 7Mfr8shT4pXWBr/plN+uNkIabdM= + Headers/StosJIT-Swift.h h9vaTwhC6FlnyKmIkaxLQGlFd1g= @@ -26,7 +30,7 @@ Modules/StosJIT.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo - 2mJoWBgg56N+3OxKfIDMLZFNHVk= + nihJghwM5m7kxkQD7UvrWyHkLy8= Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json @@ -79,7 +83,7 @@ hash2 - sZBe57nozztJzv83RPLjKIRYGSQmeE7XYCqr63xZONM= + +Ehvco7cQbAaF7zufvBYTiGXFp37Hjym/Pav514sGPk= Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index a1e6db971..82489673f 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -159,7 +159,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RunCommand(Span memory, ThreadedRenderer threaded, IRenderer renderer) { - _lookup[memory[^1]](memory, threaded, renderer); + try + { + _lookup[memory[^1]](memory, threaded, renderer); + } + catch (Exception ex) + { + // I have no idea what i'm doing, doing this to see if i can avoid MoltenVK crashes in games. + } } } } diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs index 117f79bb4..224f80b53 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs @@ -31,9 +31,16 @@ namespace Ryujinx.Graphics.Vulkan _gd = gd; _device = device; - // Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order. + // Calculate total number of individual descriptors + int totalDescriptors = 0; + for (int seg = 0; seg < segments.Length; seg++) + { + totalDescriptors += segments[seg].Count; + } - DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segments.Length]; + + DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[totalDescriptors]; + int entryIndex = 0; nuint structureOffset = 0; for (int seg = 0; seg < segments.Length; seg++) @@ -42,45 +49,36 @@ namespace Ryujinx.Graphics.Vulkan int binding = segment.Binding; int count = segment.Count; + DescriptorType descriptorType = segment.Type.Convert(); - if (IsBufferType(segment.Type)) + // Create separate entries for each descriptor in this segment + for (int i = 0; i < count; i++) { - entries[seg] = new DescriptorUpdateTemplateEntry() + nuint stride; + if (IsBufferType(segment.Type)) { - DescriptorType = segment.Type.Convert(), - DstBinding = (uint)binding, - DescriptorCount = (uint)count, + stride = (nuint)Unsafe.SizeOf(); + } + else if (IsBufferTextureType(segment.Type)) + { + stride = (nuint)Unsafe.SizeOf(); + } + else + { + stride = (nuint)Unsafe.SizeOf(); + } + + entries[entryIndex] = new DescriptorUpdateTemplateEntry() + { + DescriptorType = descriptorType, + DstBinding = (uint)(binding + i), + DescriptorCount = 1, // Always 1 descriptor per entry Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() + Stride = stride }; - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } - else if (IsBufferTextureType(segment.Type)) - { - entries[seg] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = segment.Type.Convert(), - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; - - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } - else - { - entries[seg] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = segment.Type.Convert(), - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; - - structureOffset += (nuint)(Unsafe.SizeOf() * count); + structureOffset += stride; + entryIndex++; } } @@ -89,7 +87,7 @@ namespace Ryujinx.Graphics.Vulkan var info = new DescriptorUpdateTemplateCreateInfo() { SType = StructureType.DescriptorUpdateTemplateCreateInfo, - DescriptorUpdateEntryCount = (uint)segments.Length, + DescriptorUpdateEntryCount = (uint)totalDescriptors, PDescriptorUpdateEntries = entries, TemplateType = DescriptorUpdateTemplateType.DescriptorSet, @@ -124,23 +122,6 @@ namespace Ryujinx.Graphics.Vulkan int entry = 0; nuint structureOffset = 0; - void AddBinding(int binding, int count) - { - entries[entry++] = new DescriptorUpdateTemplateEntry() - { - DescriptorType = DescriptorType.UniformBuffer, - DstBinding = (uint)binding, - DescriptorCount = (uint)count, - Offset = structureOffset, - Stride = (nuint)Unsafe.SizeOf() - }; - - structureOffset += (nuint)(Unsafe.SizeOf() * count); - } - - int startBinding = 0; - int bindingCount = 0; - foreach (ResourceDescriptor descriptor in descriptors.Descriptors) { for (int i = 0; i < descriptor.Count; i++) @@ -149,28 +130,21 @@ namespace Ryujinx.Graphics.Vulkan if ((updateMask & (1L << binding)) != 0) { - if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding)) + entries[entry] = new DescriptorUpdateTemplateEntry() { - AddBinding(startBinding, bindingCount); + DescriptorType = DescriptorType.UniformBuffer, + DstBinding = (uint)binding, + DescriptorCount = 1, // Always 1 descriptor per entry + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; - bindingCount = 0; - } - - if (bindingCount == 0) - { - startBinding = binding; - } - - bindingCount++; + structureOffset += (nuint)Unsafe.SizeOf(); + entry++; } } } - if (bindingCount > 0) - { - AddBinding(startBinding, bindingCount); - } - Size = (int)structureOffset; var info = new DescriptorUpdateTemplateCreateInfo() diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 1d0409f0e..3f6547396 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -3,10 +3,10 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; -using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Buffer = Silk.NET.Vulkan.Buffer; using CompareOp = Ryujinx.Graphics.GAL.CompareOp; using Format = Ryujinx.Graphics.GAL.Format; using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; @@ -141,11 +141,11 @@ namespace Ryujinx.Graphics.Vulkan _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2]; _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; - _textureArrayRefs = Array.Empty>(); - _imageArrayRefs = Array.Empty>(); + _textureArrayRefs = []; + _imageArrayRefs = []; - _textureArrayExtraRefs = Array.Empty>(); - _imageArrayExtraRefs = Array.Empty>(); + _textureArrayExtraRefs = []; + _imageArrayExtraRefs = []; _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings]; _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings]; @@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Vulkan _uniformSetPd = new int[Constants.MaxUniformBufferBindings]; - var initialImageInfo = new DescriptorImageInfo + DescriptorImageInfo initialImageInfo = new() { ImageLayout = ImageLayout.General, }; @@ -217,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan if (isMainPipeline) { - FeedbackLoopHazards = new(); + FeedbackLoopHazards = []; } } @@ -235,7 +235,7 @@ namespace Ryujinx.Graphics.Vulkan // Check stage bindings - _uniformMirrored.Union(_uniformSet).SignalSet((int binding, int count) => + _uniformMirrored.Union(_uniformSet).SignalSet((binding, count) => { for (int i = 0; i < count; i++) { @@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan } }); - _storageMirrored.Union(_storageSet).SignalSet((int binding, int count) => + _storageMirrored.Union(_storageSet).SignalSet((binding, count) => { for (int i = 0; i < count; i++) { @@ -301,13 +301,13 @@ namespace Ryujinx.Graphics.Vulkan { for (int i = 0; i < segment.Count; i++) { - ref var texture = ref _textureRefs[segment.Binding + i]; + ref TextureRef texture = ref _textureRefs[segment.Binding + i]; texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); } } else { - ref var arrayRef = ref _textureArrayRefs[segment.Binding]; + ref ArrayRef arrayRef = ref _textureArrayRefs[segment.Binding]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } @@ -322,13 +322,13 @@ namespace Ryujinx.Graphics.Vulkan { for (int i = 0; i < segment.Count; i++) { - ref var image = ref _imageRefs[segment.Binding + i]; + ref ImageRef image = ref _imageRefs[segment.Binding + i]; image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); } } else { - ref var arrayRef = ref _imageArrayRefs[segment.Binding]; + ref ArrayRef arrayRef = ref _imageArrayRefs[segment.Binding]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } @@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.Vulkan for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++) { - var bindingSegments = _program.BindingSegments[setIndex]; + ResourceBindingSegment[] bindingSegments = _program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { @@ -348,18 +348,18 @@ namespace Ryujinx.Graphics.Vulkan if (segment.IsArray) { - if (segment.Type == ResourceType.Texture || - segment.Type == ResourceType.Sampler || - segment.Type == ResourceType.TextureAndSampler || - segment.Type == ResourceType.BufferTexture) + if (segment.Type is ResourceType.Texture or + ResourceType.Sampler or + ResourceType.TextureAndSampler or + ResourceType.BufferTexture) { - ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; + ref ArrayRef arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } - else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage) + else if (segment.Type is ResourceType.Image or ResourceType.BufferImage) { - ref var arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; + ref ArrayRef arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); } @@ -424,8 +424,8 @@ namespace Ryujinx.Graphics.Vulkan { for (int i = 0; i < buffers.Length; i++) { - var assignment = buffers[i]; - var buffer = assignment.Range; + BufferAssignment assignment = buffers[i]; + BufferRange buffer = assignment.Range; int index = assignment.Binding; Auto vkBuffer = buffer.Handle == BufferHandle.Null @@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Vulkan Range = (ulong)buffer.Size, }; - var newRef = new BufferRef(vkBuffer, ref buffer); + BufferRef newRef = new(vkBuffer, ref buffer); ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; @@ -460,7 +460,7 @@ namespace Ryujinx.Graphics.Vulkan { for (int i = 0; i < buffers.Length; i++) { - var vkBuffer = buffers[i]; + Auto vkBuffer = buffers[i]; int index = first + i; ref BufferRef currentBufferRef = ref _storageBufferRefs[index]; @@ -633,8 +633,8 @@ namespace Ryujinx.Graphics.Vulkan { for (int i = 0; i < buffers.Length; i++) { - var assignment = buffers[i]; - var buffer = assignment.Range; + BufferAssignment assignment = buffers[i]; + BufferRange buffer = assignment.Range; int index = assignment.Binding; Auto vkBuffer = buffer.Handle == BufferHandle.Null @@ -678,7 +678,7 @@ namespace Ryujinx.Graphics.Vulkan return; } - var program = _program; + ShaderCollection program = _program; if (_dirty.HasFlag(DirtyFlags.Uniform)) { @@ -699,13 +699,19 @@ namespace Ryujinx.Graphics.Vulkan if (_dirty.HasFlag(DirtyFlags.Texture)) { - if (program.UpdateTexturesWithoutTemplate) + if (false) { UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); } else { - UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); + try { + UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); + } + catch (Exception e) + { + UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); + } } } @@ -757,18 +763,17 @@ namespace Ryujinx.Graphics.Vulkan return mirrored; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBind(CommandBufferScoped cbs, ShaderCollection program, int setIndex, PipelineBindPoint pbp) { - var bindingSegments = program.BindingSegments[setIndex]; + ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { return; } - var dummyBuffer = _dummyBuffer?.GetBuffer(); + Auto dummyBuffer = _dummyBuffer?.GetBuffer(); if (_updateDescriptorCacheCbIndex) { @@ -776,7 +781,7 @@ namespace Ryujinx.Graphics.Vulkan program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); } - var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs); + DescriptorSetCollection dsc = program.GetNewDescriptorSetCollection(setIndex, out bool isNew).Get(cbs); if (!program.HasMinimalLayout) { @@ -811,12 +816,9 @@ namespace Ryujinx.Graphics.Vulkan } } - // Split buffer updates into individual slices for MoltenVK compatibility ReadOnlySpan uniformBuffers = _uniformBuffers; - for (int i = 0; i < count; i++) - { - tu.Push(uniformBuffers.Slice(binding + i, 1)); - } + + tu.Push(uniformBuffers.Slice(binding, count)); } else if (setIndex == PipelineBase.StorageSetIndex) { @@ -828,7 +830,7 @@ namespace Ryujinx.Graphics.Vulkan if (_storageSet.Set(index)) { - ref var info = ref _storageBuffers[index]; + ref DescriptorBufferInfo info = ref _storageBuffers[index]; bool mirrored = UpdateBuffer(cbs, ref info, @@ -840,12 +842,9 @@ namespace Ryujinx.Graphics.Vulkan } } - // Split buffer updates into individual slices for MoltenVK compatibility ReadOnlySpan storageBuffers = _storageBuffers; - for (int i = 0; i < count; i++) - { - tu.Push(storageBuffers.Slice(binding + i, 1)); - } + + tu.Push(storageBuffers.Slice(binding, count)); } else if (setIndex == PipelineBase.TextureSetIndex) { @@ -857,8 +856,8 @@ namespace Ryujinx.Graphics.Vulkan for (int i = 0; i < count; i++) { - ref var texture = ref textures[i]; - ref var refs = ref _textureRefs[binding + i]; + ref DescriptorImageInfo texture = ref textures[i]; + ref TextureRef refs = ref _textureRefs[binding + i]; texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; @@ -874,10 +873,7 @@ namespace Ryujinx.Graphics.Vulkan } } - for (int i = 0; i < count; i++) - { - tu.Push(textures.Slice(i, 1)); - } + tu.Push(textures[..count]); } else { @@ -888,10 +884,7 @@ namespace Ryujinx.Graphics.Vulkan bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; } - for (int i = 0; i < count; i++) - { - tu.Push(bufferTextures.Slice(i, 1)); - } + tu.Push(bufferTextures[..count]); } } else @@ -919,10 +912,7 @@ namespace Ryujinx.Graphics.Vulkan images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default; } - for (int i = 0; i < count; i++) - { - tu.Push(images.Slice(i, 1)); - } + tu.Push(images[..count]); } else { @@ -933,10 +923,7 @@ namespace Ryujinx.Graphics.Vulkan bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, true) ?? default; } - for (int i = 0; i < count; i++) - { - tu.Push(bufferImages.Slice(i, 1)); - } + tu.Push(bufferImages[..count]); } } else @@ -953,31 +940,29 @@ namespace Ryujinx.Graphics.Vulkan } } - var sets = dsc.GetSets(); + DescriptorSet[] sets = dsc.GetSets(); _templateUpdater.Commit(_gd, _device, sets[0]); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) { int setIndex = PipelineBase.TextureSetIndex; - var bindingSegments = program.BindingSegments[setIndex]; + ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { return; } - var dummyImageInfo = new DescriptorImageInfo + if (_updateDescriptorCacheCbIndex) { - ImageView = _dummyTexture.GetImageView().Get(cbs).Value, - Sampler = _dummySampler.GetSampler().Get(cbs).Value, - ImageLayout = ImageLayout.General - }; + _updateDescriptorCacheCbIndex = false; + program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); + } - var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs); + DescriptorSetCollection dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs); foreach (ResourceBindingSegment segment in bindingSegments) { @@ -988,78 +973,56 @@ namespace Ryujinx.Graphics.Vulkan { if (segment.Type != ResourceType.BufferTexture) { + Span textures = _textures; + for (int i = 0; i < count; i++) { - int index = binding + i; - ref var textureRef = ref _textureRefs[index]; + ref DescriptorImageInfo texture = ref textures[i]; + ref TextureRef refs = ref _textureRefs[binding + i]; - var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView; - var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler; + texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; - var imageInfo = new DescriptorImageInfo + if (texture.ImageView.Handle == 0) { - ImageView = imageView.Handle != 0 ? imageView : dummyImageInfo.ImageView, - Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler, - ImageLayout = ImageLayout.General - }; + texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + } - dsc.UpdateImages(0, index, new[] { imageInfo }, DescriptorType.CombinedImageSampler); + if (texture.Sampler.Handle == 0) + { + texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; + } } + + dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler); } else { + Span bufferTextures = _bufferTextures; + for (int i = 0; i < count; i++) { - int index = binding + i; - var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default; - dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer); + bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; } + + dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer); } } else { - var arrayRef = _textureArrayRefs[binding]; - if (segment.Type != ResourceType.BufferTexture) { - var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler); - if (imageInfos != null) - { - for (int i = 0; i < imageInfos.Length && i < count; i++) - { - dsc.UpdateImages(0, binding + i, new[] { imageInfos[i] }, DescriptorType.CombinedImageSampler); - } - } - else - { - for (int i = 0; i < count; i++) - { - dsc.UpdateImages(0, binding + i, new[] { dummyImageInfo }, DescriptorType.CombinedImageSampler); - } - } + dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler); } else { - var bufferViews = arrayRef.Array.GetBufferViews(cbs); - if (bufferViews != null) - { - for (int i = 0; i < bufferViews.Length && i < count; i++) - { - dsc.UpdateBufferImages(0, binding + i, new[] { bufferViews[i] }, DescriptorType.UniformTexelBuffer); - } - } - else - { - for (int i = 0; i < count; i++) - { - dsc.UpdateBufferImages(0, binding + i, new[] { default(BufferView) }, DescriptorType.UniformTexelBuffer); - } - } + dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer); } } } - var sets = dsc.GetSets(); + DescriptorSet[] sets = dsc.GetSets(); + _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } @@ -1067,8 +1030,8 @@ namespace Ryujinx.Graphics.Vulkan private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs) { int sequence = _pdSequence; - var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; - var dummyBuffer = _dummyBuffer?.GetBuffer(); + ResourceBindingSegment[] bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; + Auto dummyBuffer = _dummyBuffer?.GetBuffer(); long updatedBindings = 0; DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf()); @@ -1115,12 +1078,12 @@ namespace Ryujinx.Graphics.Vulkan private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc) { // We don't support clearing texture descriptors currently. - if (setIndex != PipelineBase.UniformSetIndex && setIndex != PipelineBase.StorageSetIndex) + if (setIndex is not PipelineBase.UniformSetIndex and not PipelineBase.StorageSetIndex) { return; } - var dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default; + Buffer dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default; foreach (ResourceBindingSegment segment in _program.ClearSegments[setIndex]) { @@ -1132,7 +1095,7 @@ namespace Ryujinx.Graphics.Vulkan { for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < program.BindingSegments.Length; setIndex++) { - var bindingSegments = program.BindingSegments[setIndex]; + ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { @@ -1145,10 +1108,10 @@ namespace Ryujinx.Graphics.Vulkan { DescriptorSet[] sets = null; - if (segment.Type == ResourceType.Texture || - segment.Type == ResourceType.Sampler || - segment.Type == ResourceType.TextureAndSampler || - segment.Type == ResourceType.BufferTexture) + if (segment.Type is ResourceType.Texture or + ResourceType.Sampler or + ResourceType.TextureAndSampler or + ResourceType.BufferTexture) { sets = _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets( _device, @@ -1159,7 +1122,7 @@ namespace Ryujinx.Graphics.Vulkan _dummyTexture, _dummySampler); } - else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage) + else if (segment.Type is ResourceType.Image or ResourceType.BufferImage) { sets = _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets( _device, @@ -1230,4 +1193,4 @@ namespace Ryujinx.Graphics.Vulkan Dispose(true); } } -} +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs index 019286d28..439fcb8ea 100644 --- a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs +++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs @@ -186,20 +186,18 @@ namespace Ryujinx.Graphics.Vulkan return sets; } - DescriptorSetTemplate template = program.Templates[setIndex]; - - DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); + var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs); if (!_isBuffer) { - tu.Push(GetImageInfos(_gd, cbs, dummyTexture)); + dsc.UpdateImages(0, 0, GetImageInfos(_gd, cbs, dummyTexture), DescriptorType.StorageImage); } else { - tu.Push(GetBufferViews(cbs)); + dsc.UpdateBufferImages(0, 0, GetBufferViews(cbs), DescriptorType.StorageTexelBuffer); } - templateUpdater.Commit(_gd, device, sets[0]); + sets = dsc.GetSets(); return sets; } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 5c9497920..5f3e91cdc 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -785,7 +785,7 @@ namespace Ryujinx.Graphics.Vulkan shaderSubgroupSize: (int)Capabilities.SubgroupSize, storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment, textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment, - gatherBiasPrecision: (int)Capabilities.SubTexelPrecisionBits, //IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0, + gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0, maximumGpuMemory: GetTotalGPUMemory()); } diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index 955fee4b5..63ab44299 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -21,25 +21,25 @@ namespace Ryujinx.HLE /// The virtual file system used by the FS service. /// /// This cannot be changed after instantiation. - internal readonly VirtualFileSystem VirtualFileSystem; + public readonly VirtualFileSystem VirtualFileSystem; /// /// The manager for handling a LibHac Horizon instance. /// /// This cannot be changed after instantiation. - internal readonly LibHacHorizonManager LibHacHorizonManager; + public readonly LibHacHorizonManager LibHacHorizonManager; /// /// The account manager used by the account service. /// /// This cannot be changed after instantiation. - internal readonly AccountManager AccountManager; + public readonly AccountManager AccountManager; /// /// The content manager used by the NCM service. /// /// This cannot be changed after instantiation. - internal readonly ContentManager ContentManager; + public readonly ContentManager ContentManager; /// /// The persistent information between run for multi-application capabilities. @@ -51,93 +51,93 @@ namespace Ryujinx.HLE /// The GPU renderer to use for all GPU operations. /// /// This cannot be changed after instantiation. - internal readonly IRenderer GpuRenderer; + public readonly IRenderer GpuRenderer; /// /// The audio device driver to use for all audio operations. /// /// This cannot be changed after instantiation. - internal readonly IHardwareDeviceDriver AudioDeviceDriver; + public readonly IHardwareDeviceDriver AudioDeviceDriver; /// /// The handler for various UI related operations needed outside of HLE. /// /// This cannot be changed after instantiation. - internal readonly IHostUIHandler HostUIHandler; + public readonly IHostUIHandler HostUIHandler; /// /// Control the memory configuration used by the emulation context. /// /// This cannot be changed after instantiation. - internal readonly MemoryConfiguration MemoryConfiguration; + public readonly MemoryConfiguration MemoryConfiguration; /// /// The system language to use in the settings service. /// /// This cannot be changed after instantiation. - internal readonly SystemLanguage SystemLanguage; + public readonly SystemLanguage SystemLanguage; /// /// The system region to use in the settings service. /// /// This cannot be changed after instantiation. - internal readonly RegionCode Region; + public readonly RegionCode Region; /// /// Control the initial state of the vertical sync in the SurfaceFlinger service. /// - internal readonly bool EnableVsync; + public readonly bool EnableVsync; /// /// Control the initial state of the docked mode. /// - internal readonly bool EnableDockedMode; + public readonly bool EnableDockedMode; /// /// Control if the Profiled Translation Cache (PTC) should be used. /// - internal readonly bool EnablePtc; + public readonly bool EnablePtc; /// /// Control if the guest application should be told that there is a Internet connection available. /// - public bool EnableInternetAccess { internal get; set; } + public bool EnableInternetAccess; /// /// Control LibHac's integrity check level. /// /// This cannot be changed after instantiation. - internal readonly IntegrityCheckLevel FsIntegrityCheckLevel; + public readonly IntegrityCheckLevel FsIntegrityCheckLevel; /// /// Control LibHac's global access logging level. Value must be between 0 and 3. /// /// This cannot be changed after instantiation. - internal readonly int FsGlobalAccessLogMode; + public readonly int FsGlobalAccessLogMode; /// /// The system time offset to apply to the time service steady and local clocks. /// /// This cannot be changed after instantiation. - internal readonly long SystemTimeOffset; + public readonly long SystemTimeOffset; /// /// The system timezone used by the time service. /// /// This cannot be changed after instantiation. - internal readonly string TimeZone; + public readonly string TimeZone; /// /// Type of the memory manager used on CPU emulation. /// - public MemoryManagerMode MemoryManagerMode { internal get; set; } + public MemoryManagerMode MemoryManagerMode { get; set; } /// /// Control the initial state of the ignore missing services setting. /// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception. /// /// TODO: Update this again. - public bool IgnoreMissingServices { internal get; set; } + public bool IgnoreMissingServices { get; set; } /// /// Aspect Ratio applied to the renderer window by the SurfaceFlinger service. @@ -152,22 +152,22 @@ namespace Ryujinx.HLE /// /// Use Hypervisor over JIT if available. /// - internal readonly bool UseHypervisor; + public readonly bool UseHypervisor; /// /// Multiplayer LAN Interface ID (device GUID) /// - public string MultiplayerLanInterfaceId { internal get; set; } + public string MultiplayerLanInterfaceId { get; set; } /// /// Multiplayer Mode /// - public MultiplayerMode MultiplayerMode { internal get; set; } + public MultiplayerMode MultiplayerMode { get; set; } /// /// An action called when HLE force a refresh of output after docked mode changed. /// - public Action RefreshInputConfig { internal get; set; } + public Action RefreshInputConfig { get; set; } public HLEConfiguration(VirtualFileSystem virtualFileSystem, LibHacHorizonManager libHacHorizonManager, diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index b1e01caa0..d04acc69c 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -396,7 +396,7 @@ namespace Ryujinx.Headless.SDL2 [UnmanagedCallersOnly(EntryPoint = "pause_emulation")] public static void PauseEmulation(bool shouldPause) { - if (_window != null) + if (_window != null && _window.Device != null) { if (!shouldPause) { @@ -1721,5 +1721,137 @@ namespace Ryujinx.Headless.SDL2 span.Clear(); Encoding.UTF8.GetBytes(source, span); } + + [UnmanagedCallersOnly(EntryPoint = "update_settings_external")] + public static unsafe int UpdateSettingsExternal(int argCount, IntPtr* pArgs) + { + string[] args = new string[argCount]; + + try + { + for (int i = 0; i < argCount; i++) + { + args[i] = Marshal.PtrToStringAnsi(pArgs[i]); + } + + Options parsedOptions = null; + Parser.Default.ParseArguments(args) + .WithParsed(opts => parsedOptions = opts); + + if (parsedOptions == null) + { + Console.WriteLine("Failed to parse options."); + return -1; + } + + ApplyDynamicSettings(parsedOptions); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + return -1; + } + + return 0; + } + + private static void ApplyDynamicSettings(Options options) + { + Graphics.Gpu.GraphicsConfig.ResScale = options.ResScale; + Graphics.Gpu.GraphicsConfig.MaxAnisotropy = options.MaxAnisotropy; + Graphics.Gpu.GraphicsConfig.EnableShaderCache = !options.DisableShaderCache; + Graphics.Gpu.GraphicsConfig.EnableTextureRecompression = options.EnableTextureRecompression; + Graphics.Gpu.GraphicsConfig.EnableMacroHLE = !options.DisableMacroHLE; + + if (_window != null) + { + _window.IsFullscreen = options.IsFullscreen; + _window.DisplayId = options.DisplayId; + _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen; + _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth; + _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight; + _window.AntiAliasing = options.AntiAliasing; + _window.ScalingFilter = options.ScalingFilter; + _window.ScalingFilterLevel = options.ScalingFilterLevel; + _window._aspectRatio = options.AspectRatio; + } + + if (_emulationContext != null) + { + _emulationContext.SetVolume(options.AudioVolume); + + _emulationContext.System.State.SetLanguage(options.SystemLanguage); + _emulationContext.System.State.SetRegion(options.SystemRegion); + _emulationContext.EnableDeviceVsync = !options.DisableVSync; + _emulationContext.System.State.DockedMode = !options.DisableDockedMode; + _emulationContext.System.EnablePtc = !options.DisablePTC; + _emulationContext.System.FsIntegrityCheckLevel = !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; + _emulationContext.System.GlobalAccessLogMode = options.FsGlobalAccessLogMode; + _emulationContext.Configuration.IgnoreMissingServices = options.IgnoreMissingServices; + _emulationContext.Configuration.AspectRatio = options.AspectRatio; + _emulationContext.Configuration.EnableInternetAccess = options.EnableInternetAccess; + _emulationContext.Configuration.MemoryManagerMode = options.MemoryManagerMode; + _emulationContext.Configuration.MultiplayerLanInterfaceId = options.MultiplayerLanInterfaceId; + } + + Logger.SetEnable(LogLevel.Debug, options.LoggingEnableDebug); + Logger.SetEnable(LogLevel.Stub, !options.LoggingDisableStub); + Logger.SetEnable(LogLevel.Info, !options.LoggingDisableInfo); + Logger.SetEnable(LogLevel.Warning, !options.LoggingDisableWarning); + Logger.SetEnable(LogLevel.Error, options.LoggingEnableError); + Logger.SetEnable(LogLevel.Trace, options.LoggingEnableTrace); + Logger.SetEnable(LogLevel.Guest, !options.LoggingDisableGuest); + Logger.SetEnable(LogLevel.AccessLog, options.LoggingEnableFsAccessLog); + } + + + // Old :3 + private static void ReplaceEmulationContextConfiguration(Switch emu, Options options) + { + var oldConfig = emu.Configuration; + + var newConfig = new HLEConfiguration( + _virtualFileSystem, + _libHacHorizonManager, + _contentManager, + _accountManager, + _userChannelPersistence, + oldConfig.GpuRenderer, + oldConfig.AudioDeviceDriver, + oldConfig.MemoryConfiguration, + oldConfig.HostUIHandler, + options.SystemLanguage, + options.SystemRegion, + !options.DisableVSync, + !options.DisableDockedMode, + !options.DisablePTC, + options.EnableInternetAccess, + !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + options.FsGlobalAccessLogMode, + options.SystemTimeOffset, + options.SystemTimeZone, + options.MemoryManagerMode, + options.IgnoreMissingServices, + options.AspectRatio, + options.AudioVolume, + options.UseHypervisor, + options.MultiplayerLanInterfaceId, + Ryujinx.Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm + ); + + var configField = typeof(Switch).GetField("k__BackingField", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + if (configField != null) + { + configField.SetValue(emu, newConfig); + } + + emu.System.State.SetLanguage(newConfig.SystemLanguage); + emu.System.State.SetRegion(newConfig.Region); + emu.EnableDeviceVsync = newConfig.EnableVsync; + emu.System.State.DockedMode = newConfig.EnableDockedMode; + emu.System.EnablePtc = newConfig.EnablePtc; + emu.System.FsIntegrityCheckLevel = newConfig.FsIntegrityCheckLevel; + emu.System.GlobalAccessLogMode = newConfig.FsGlobalAccessLogMode; + } } } diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index 77896817a..624089254 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -85,7 +85,7 @@ namespace Ryujinx.Headless.SDL2 private string _gpuDriverName; - private readonly AspectRatio _aspectRatio; + public AspectRatio _aspectRatio; private readonly bool _enableMouse; public WindowBase( @@ -162,7 +162,7 @@ namespace Ryujinx.Headless.SDL2 private void InitializeWindow() { - if (this is Ryujinx.Headless.SDL2.Vulkan.MoltenVKWindow) { + if (this is Vulkan.MoltenVKWindow) { string message = $"Not using SDL Windows, Skipping..."; Logger.Info?.Print(LogClass.Application, message);