App Icon Switcher, Per-App Settings Page, Update Virtual Controller and more
|
@ -75,7 +75,7 @@ namespace ARMeilleure.Translation
|
||||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
||||||
Stubs = new TranslatorStubs(FunctionTable);
|
Stubs = new TranslatorStubs(FunctionTable);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.DispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
||||||
|
|
|
@ -8,4 +8,4 @@
|
||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
VERSION = 1.7.0
|
VERSION = 2.0
|
||||||
|
|
|
@ -32,13 +32,6 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
4E59B0A32DEA5CA9004BFF2A /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
|
|
||||||
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
|
|
||||||
};
|
|
||||||
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
|
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
@ -53,6 +46,13 @@
|
||||||
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
||||||
remoteInfo = MeloNX;
|
remoteInfo = MeloNX;
|
||||||
};
|
};
|
||||||
|
4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
|
||||||
|
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
|
||||||
|
};
|
||||||
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
|
BD43C6252D1B249E003BBC42 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
@ -294,7 +294,7 @@
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
4E59B0A42DEA5CA9004BFF2A /* PBXTargetDependency */,
|
4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
|
@ -482,11 +482,6 @@
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
4E59B0A42DEA5CA9004BFF2A /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
|
|
||||||
targetProxy = 4E59B0A32DEA5CA9004BFF2A /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
|
@ -497,6 +492,11 @@
|
||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
4EFFCD192DFB766F00F78EA6 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
|
||||||
|
targetProxy = 4EFFCD182DFB766F00F78EA6 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
|
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
|
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
|
||||||
|
@ -647,13 +647,16 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = PixelAppIcon;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
|
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = NO;
|
ENABLE_TESTABILITY = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
|
@ -1025,13 +1056,16 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = PixelAppIcon;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
|
CODE_SIGN_ENTITLEMENTS = MeloNX/MeloNX.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
GCC_OPTIMIZATION_LEVEL = z;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
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",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(VERSION)";
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
|
|
|
@ -59,6 +59,8 @@ void initialize();
|
||||||
|
|
||||||
int main_ryujinx_sdl(int argc, char **argv);
|
int main_ryujinx_sdl(int argc, char **argv);
|
||||||
|
|
||||||
|
int update_settings_external(int argc, char **argv);
|
||||||
|
|
||||||
int get_current_fps();
|
int get_current_fps();
|
||||||
|
|
||||||
void touch_began(float x, float y, int index);
|
void touch_began(float x, float y, int index);
|
||||||
|
|
|
@ -9,8 +9,6 @@ import Foundation
|
||||||
import Network
|
import Network
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func stikJITorStikDebug() -> Int {
|
func stikJITorStikDebug() -> Int {
|
||||||
let teamid = SecTaskCopyTeamIdentifier(SecTaskCreateFromSelf(nil)!, nil)
|
let teamid = SecTaskCopyTeamIdentifier(SecTaskCreateFromSelf(nil)!, nil)
|
||||||
|
|
||||||
|
@ -25,15 +23,28 @@ func stikJITorStikDebug() -> Int {
|
||||||
return 0 // Not Found
|
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 {
|
func checkifappinstalled(_ id: String) -> Bool {
|
||||||
|
|
||||||
guard let handle = dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_LAZY) else {
|
guard let handle = dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_LAZY) else {
|
||||||
if let error = dlerror() {
|
|
||||||
print(String(cString: error))
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
// fatalError("Failed to open dylib")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias SBSLaunchApplicationWithIdentifierFunc = @convention(c) (CFString, Bool) -> Int32
|
typealias SBSLaunchApplicationWithIdentifierFunc = @convention(c) (CFString, Bool) -> Int32
|
||||||
|
|
|
@ -356,7 +356,9 @@ class Ryujinx : ObservableObject {
|
||||||
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
||||||
|
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
self.isRunning = false
|
DispatchQueue.main.async {
|
||||||
|
self.isRunning = false
|
||||||
|
}
|
||||||
if let accessing, accessing {
|
if let accessing, accessing {
|
||||||
url!.stopAccessingSecurityScopedResource()
|
url!.stopAccessingSecurityScopedResource()
|
||||||
}
|
}
|
||||||
|
@ -365,7 +367,9 @@ class Ryujinx : ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
self.isRunning = false
|
DispatchQueue.main.async {
|
||||||
|
self.isRunning = false
|
||||||
|
}
|
||||||
Thread.sleep(forTimeInterval: 0.3)
|
Thread.sleep(forTimeInterval: 0.3)
|
||||||
let logs = LogCapture.shared.capturedLogs
|
let logs = LogCapture.shared.capturedLogs
|
||||||
let parsedLogs = extractExceptionInfo(logs)
|
let parsedLogs = extractExceptionInfo(logs)
|
||||||
|
@ -384,14 +388,19 @@ class Ryujinx : ObservableObject {
|
||||||
|
|
||||||
|
|
||||||
presentAlert(title: "MeloNX Crashed!", message: parsedLogs.exceptionType + ": " + parsedLogs.message) {
|
presentAlert(title: "MeloNX Crashed!", message: parsedLogs.exceptionType + ": " + parsedLogs.message) {
|
||||||
|
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||||
assert(true, parsedLogs.exceptionType)
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
presentAlert(title: "MeloNX Crashed!", message: "Unknown Error") {
|
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] = []
|
var args: [String] = []
|
||||||
|
|
||||||
// Add the game path
|
// 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)
|
// Append the input dsu servers (limit to 8 (used to be 4) just in case)
|
||||||
if !config.inputDSUServers.isEmpty {
|
if !config.inputDSUServers.isEmpty {
|
||||||
config.inputDSUServers.prefix(8).enumerated().forEach { index, inputDSUServer in
|
config.inputDSUServers.prefix(8).enumerated().forEach { index, inputDSUServer in
|
||||||
if config.handHeldController {
|
if index == 0 {
|
||||||
args.append(contentsOf: ["\(index == 0 ? "--input-dsu-server-handheld" : "--input-dsu-server-\(index + 1)")", inputDSUServer])
|
args.append(contentsOf: ["--input-dsu-server-handheld", inputDSUServer])
|
||||||
} else {
|
|
||||||
args.append(contentsOf: ["--input-dsu-server-\(index + 1)", inputDSUServer])
|
|
||||||
}
|
}
|
||||||
|
args.append(contentsOf: ["--input-dsu-server-\(index + 1)", inputDSUServer])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,6 +264,7 @@ struct ABXYView: View {
|
||||||
|
|
||||||
struct ButtonView: View {
|
struct ButtonView: View {
|
||||||
var button: VirtualControllerButton
|
var button: VirtualControllerButton
|
||||||
|
var callback: (() -> Void)? = nil
|
||||||
|
|
||||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
@ -344,23 +345,29 @@ struct ButtonView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleButtonPress() {
|
private func handleButtonPress() {
|
||||||
guard !isPressed || istoggle else { return }
|
if let callback {
|
||||||
|
callback()
|
||||||
if istoggle {
|
|
||||||
toggleState.toggle()
|
|
||||||
isPressed = toggleState
|
|
||||||
let value = toggleState ? 1 : 0
|
|
||||||
Ryujinx.shared.virtualController.setButtonState(Uint8(value), for: button)
|
|
||||||
Haptics.shared.play(.medium)
|
|
||||||
} else {
|
} else {
|
||||||
isPressed = true
|
guard !isPressed || istoggle else { return }
|
||||||
Ryujinx.shared.virtualController.setButtonState(1, for: button)
|
|
||||||
Haptics.shared.play(.medium)
|
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() {
|
private func handleButtonRelease() {
|
||||||
if istoggle { return }
|
if istoggle { return }
|
||||||
|
|
||||||
|
if let callback { return }
|
||||||
|
|
||||||
guard isPressed else { return }
|
guard isPressed else { return }
|
||||||
|
|
||||||
|
@ -397,40 +404,23 @@ struct ButtonView: View {
|
||||||
// Centralized button configuration
|
// Centralized button configuration
|
||||||
private var buttonConfig: ButtonConfiguration {
|
private var buttonConfig: ButtonConfiguration {
|
||||||
switch button {
|
switch button {
|
||||||
case .A:
|
case .A: return .init(iconName: "a.circle.fill")
|
||||||
return ButtonConfiguration(iconName: "a.circle.fill")
|
case .B: return .init(iconName: "b.circle.fill")
|
||||||
case .B:
|
case .X: return .init(iconName: "x.circle.fill")
|
||||||
return ButtonConfiguration(iconName: "b.circle.fill")
|
case .Y: return .init(iconName: "y.circle.fill")
|
||||||
case .X:
|
case .leftStick: return .init(iconName: "l.joystick.press.down.fill")
|
||||||
return ButtonConfiguration(iconName: "x.circle.fill")
|
case .rightStick: return .init(iconName: "r.joystick.press.down.fill")
|
||||||
case .Y:
|
case .dPadUp: return .init(iconName: "arrowtriangle.up.circle.fill")
|
||||||
return ButtonConfiguration(iconName: "y.circle.fill")
|
case .dPadDown: return .init(iconName: "arrowtriangle.down.circle.fill")
|
||||||
case .leftStick:
|
case .dPadLeft: return .init(iconName: "arrowtriangle.left.circle.fill")
|
||||||
return ButtonConfiguration(iconName: "l.joystick.press.down.fill")
|
case .dPadRight: return .init(iconName: "arrowtriangle.right.circle.fill")
|
||||||
case .rightStick:
|
case .leftTrigger: return .init(iconName: "zl.rectangle.roundedtop.fill")
|
||||||
return ButtonConfiguration(iconName: "r.joystick.press.down.fill")
|
case .rightTrigger: return .init(iconName: "zr.rectangle.roundedtop.fill")
|
||||||
case .dPadUp:
|
case .leftShoulder: return .init(iconName: "l.rectangle.roundedbottom.fill")
|
||||||
return ButtonConfiguration(iconName: "arrowtriangle.up.circle.fill")
|
case .rightShoulder: return .init(iconName: "r.rectangle.roundedbottom.fill")
|
||||||
case .dPadDown:
|
case .start: return .init(iconName: "plus.circle.fill")
|
||||||
return ButtonConfiguration(iconName: "arrowtriangle.down.circle.fill")
|
case .back: return .init(iconName: "minus.circle.fill")
|
||||||
case .dPadLeft:
|
case .guide: return .init(iconName: "gearshape.fill")
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,3 +428,121 @@ struct ButtonView: View {
|
||||||
let iconName: String
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct Joystick: View {
|
||||||
@State private var offset: CGSize = .zero
|
@State private var offset: CGSize = .zero
|
||||||
@Binding var showBackground: Bool
|
@Binding var showBackground: Bool
|
||||||
|
|
||||||
let sensitivity: CGFloat = 1.5
|
let sensitivity: CGFloat = 1.2
|
||||||
|
|
||||||
|
|
||||||
var dragGesture: some Gesture {
|
var dragGesture: some Gesture {
|
||||||
|
|
|
@ -28,7 +28,6 @@ struct JoystickController: View {
|
||||||
VStack {
|
VStack {
|
||||||
Joystick(position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground)
|
Joystick(position: $position, joystickSize: dragDiameter * 0.2, boundarySize: dragDiameter, showBackground: $showBackground)
|
||||||
.onChange(of: position) { newValue in
|
.onChange(of: position) { newValue in
|
||||||
|
|
||||||
if iscool != nil {
|
if iscool != nil {
|
||||||
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: newValue.x, y: newValue.y)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,6 +24,8 @@ struct EmulationView: View {
|
||||||
|
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) var scenePhase
|
||||||
@State private var isInBackground = false
|
@State private var isInBackground = false
|
||||||
|
@State var showSettings = false
|
||||||
|
@State var pauseEmu = true
|
||||||
@AppStorage("location-enabled") var locationenabled: Bool = false
|
@AppStorage("location-enabled") var locationenabled: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -80,15 +82,47 @@ struct EmulationView: View {
|
||||||
if ssb {
|
if ssb {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Image(systemName: "arrow.left.circle")
|
Menu {
|
||||||
.resizable()
|
|
||||||
.frame(width: 50, height: 50)
|
/*
|
||||||
.onTapGesture {
|
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
|
startgame = nil
|
||||||
stop_emulation()
|
stop_emulation()
|
||||||
try? Ryujinx.shared.stop()
|
try? Ryujinx.shared.stop()
|
||||||
|
} label: {
|
||||||
|
Label {
|
||||||
|
Text("Exit (Unstable)")
|
||||||
|
} icon: {
|
||||||
|
Image(systemName: "x.circle")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
} label: {
|
||||||
|
ExtButtonIconView(button: .guide, opacity: 0.4)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -122,5 +156,11 @@ struct EmulationView: View {
|
||||||
isInBackground = true
|
isInBackground = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showSettings) {
|
||||||
|
// PerGameSettingsView(titleId: startgame?.titleId ?? "", manager: InGameSettingsManager.shared)
|
||||||
|
// .onDisappear() {
|
||||||
|
// InGameSettingsManager.shared.saveSettings()
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() ?? [:]
|
||||||
|
}
|
||||||
|
}
|
|
@ -357,8 +357,16 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StateObject private var persettings = PerGameSettingsManager.shared
|
||||||
private func start(displayid: UInt32) {
|
private func start(displayid: UInt32) {
|
||||||
guard let game else { return }
|
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.gamepath = game.fileURL.path
|
||||||
config.inputids = Array(Set(currentControllers.map(\.id)))
|
config.inputids = Array(Set(currentControllers.map(\.id)))
|
||||||
|
@ -367,9 +375,7 @@ struct ContentView: View {
|
||||||
|
|
||||||
registerMotionForMatchingControllers()
|
registerMotionForMatchingControllers()
|
||||||
|
|
||||||
if config.inputids.isEmpty {
|
config.inputids.isEmpty ? config.inputids.append("0") : ()
|
||||||
config.inputids.append("0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local DSU loopback to ryujinx per input id
|
// Local DSU loopback to ryujinx per input id
|
||||||
for _ in config.inputids {
|
for _ in config.inputids {
|
||||||
|
|
|
@ -26,6 +26,15 @@ struct GameLibraryView: View {
|
||||||
@State var startgame = false
|
@State var startgame = false
|
||||||
@State var isSelectingGameFile = false
|
@State var isSelectingGameFile = false
|
||||||
@State var isViewingGameInfo: Bool = false
|
@State var isViewingGameInfo: Bool = false
|
||||||
|
@State var gamePerGameSettings: Game?
|
||||||
|
var isShowingPerGameSettings: Binding<Bool> {
|
||||||
|
Binding<Bool> {
|
||||||
|
gamePerGameSettings != nil
|
||||||
|
} set: { value in
|
||||||
|
!value ? gamePerGameSettings = nil : ()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@State var isSelectingGameUpdate: Bool = false
|
@State var isSelectingGameUpdate: Bool = false
|
||||||
@State var isSelectingGameDLC: Bool = false
|
@State var isSelectingGameDLC: Bool = false
|
||||||
@StateObject var ryujinx = Ryujinx.shared
|
@StateObject var ryujinx = Ryujinx.shared
|
||||||
|
@ -201,6 +210,9 @@ struct GameLibraryView: View {
|
||||||
.sheet(isPresented: $isSelectingGameDLC) {
|
.sheet(isPresented: $isSelectingGameDLC) {
|
||||||
DLCManagerSheet(game: $gameInfo)
|
DLCManagerSheet(game: $gameInfo)
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: isShowingPerGameSettings) {
|
||||||
|
PerGameSettingsView(titleId: gamePerGameSettings!.titleId)
|
||||||
|
}
|
||||||
.sheet(isPresented: Binding(
|
.sheet(isPresented: Binding(
|
||||||
get: { isViewingGameInfo && gameInfo != nil },
|
get: { isViewingGameInfo && gameInfo != nil },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
|
@ -271,7 +283,8 @@ struct GameLibraryView: View {
|
||||||
isSelectingGameUpdate: $isSelectingGameUpdate,
|
isSelectingGameUpdate: $isSelectingGameUpdate,
|
||||||
isSelectingGameDLC: $isSelectingGameDLC,
|
isSelectingGameDLC: $isSelectingGameDLC,
|
||||||
gameRequirements: $gameRequirements,
|
gameRequirements: $gameRequirements,
|
||||||
gameInfo: $gameInfo
|
gameInfo: $gameInfo,
|
||||||
|
perGameSettings: $gamePerGameSettings
|
||||||
)
|
)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
@ -288,7 +301,8 @@ struct GameLibraryView: View {
|
||||||
isSelectingGameUpdate: $isSelectingGameUpdate,
|
isSelectingGameUpdate: $isSelectingGameUpdate,
|
||||||
isSelectingGameDLC: $isSelectingGameDLC,
|
isSelectingGameDLC: $isSelectingGameDLC,
|
||||||
gameRequirements: $gameRequirements,
|
gameRequirements: $gameRequirements,
|
||||||
gameInfo: $gameInfo
|
gameInfo: $gameInfo,
|
||||||
|
perGameSettings: $gamePerGameSettings
|
||||||
)
|
)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
@ -482,6 +496,12 @@ struct GameLibraryView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("Game Info", systemImage: "info.circle")
|
Label("Game Info", systemImage: "info.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
gamePerGameSettings = game
|
||||||
|
} label: {
|
||||||
|
Label("\(game.titleName) Settings", systemImage: "gear")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
@ -501,6 +521,12 @@ struct GameLibraryView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
removeFromRecentGames(game)
|
||||||
|
} label: {
|
||||||
|
Label("Remove from Recents", systemImage: "trash")
|
||||||
|
}
|
||||||
|
|
||||||
if #available(iOS 15, *) {
|
if #available(iOS 15, *) {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
deleteGame(game: game)
|
deleteGame(game: game)
|
||||||
|
@ -771,6 +797,8 @@ struct GameListRow: View {
|
||||||
@Binding var isSelectingGameDLC: Bool
|
@Binding var isSelectingGameDLC: Bool
|
||||||
@Binding var gameRequirements: [GameRequirements]
|
@Binding var gameRequirements: [GameRequirements]
|
||||||
@Binding var gameInfo: Game?
|
@Binding var gameInfo: Game?
|
||||||
|
@StateObject private var settingsManager = PerGameSettingsManager.shared
|
||||||
|
@Binding var perGameSettings: Game?
|
||||||
@State var gametoDelete: Game?
|
@State var gametoDelete: Game?
|
||||||
@State var showGameDeleteConfirmation: Bool = false
|
@State var showGameDeleteConfirmation: Bool = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@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()
|
Spacer()
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
@ -897,6 +933,12 @@ struct GameListRow: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("Game Info", systemImage: "info.circle")
|
Label("Game Info", systemImage: "info.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
perGameSettings = game
|
||||||
|
} label: {
|
||||||
|
Label("\(game.titleName) Settings", systemImage: "gear")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
@ -959,10 +1001,7 @@ struct GameListRow: View {
|
||||||
Text("Are you sure you want to delete \(gametoDelete?.titleName ?? "this game")?")
|
Text("Are you sure you want to delete \(gametoDelete?.titleName ?? "this game")?")
|
||||||
}
|
}
|
||||||
.listRowInsets(EdgeInsets())
|
.listRowInsets(EdgeInsets())
|
||||||
.background(
|
.wow(colorScheme)
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5))
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
startemu = game
|
startemu = game
|
||||||
|
@ -1196,3 +1235,20 @@ func pullGameCompatibility(completion: @escaping (Result<[GameRequirements], Err
|
||||||
|
|
||||||
task.resume()
|
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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Ryujinx.Arguments> {
|
||||||
|
return Binding<Ryujinx.Arguments> {
|
||||||
|
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<Content: View>: 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -266,6 +266,8 @@ struct SettingsViewNew: View {
|
||||||
|
|
||||||
@AppStorage("runOnMainThread") var runOnMainThread = false
|
@AppStorage("runOnMainThread") var runOnMainThread = false
|
||||||
|
|
||||||
|
@AppStorage("oldSettingsUI") var oldSettingsUI = false
|
||||||
|
|
||||||
@AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState()
|
@AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState()
|
||||||
|
|
||||||
let totalMemory = ProcessInfo.processInfo.physicalMemory
|
let totalMemory = ProcessInfo.processInfo.physicalMemory
|
||||||
|
@ -275,6 +277,7 @@ struct SettingsViewNew: View {
|
||||||
@State private var showResolutionInfo = false
|
@State private var showResolutionInfo = false
|
||||||
@State private var showAnisotropicInfo = false
|
@State private var showAnisotropicInfo = false
|
||||||
@State private var showControllerInfo = false
|
@State private var showControllerInfo = false
|
||||||
|
@State private var showAppIconSwitcher = false
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@AppStorage("portal") var gamepo = false
|
@AppStorage("portal") var gamepo = false
|
||||||
@StateObject var ryujinx = Ryujinx.shared
|
@StateObject var ryujinx = Ryujinx.shared
|
||||||
|
@ -327,10 +330,12 @@ struct SettingsViewNew: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
||||||
iOSSettings
|
iOSSettings
|
||||||
} else {
|
} else if !oldSettingsUI {
|
||||||
iPadOSSettings
|
iPadOSSettings
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
} else {
|
||||||
|
iOSSettings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1188,7 +1193,6 @@ struct SettingsViewNew: View {
|
||||||
|
|
||||||
|
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
|
|
||||||
TextField("Separate arguments with commas", text: binding)
|
TextField("Separate arguments with commas", text: binding)
|
||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
|
@ -1254,13 +1258,31 @@ struct SettingsViewNew: View {
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
if colorScheme == .light {
|
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()
|
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
|
// 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()
|
Divider()
|
||||||
|
|
||||||
|
@ -1275,6 +1297,13 @@ struct SettingsViewNew: View {
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
|
// Old Settings UI
|
||||||
|
SettingsToggle(isOn: $oldSettingsUI, icon: "ipad.landscape", label: "Non Switch-like Settings")
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// JIT options
|
// JIT options
|
||||||
if #available(iOS 17.0.1, *) {
|
if #available(iOS 17.0.1, *) {
|
||||||
|
@ -1401,8 +1430,6 @@ struct SVGView: UIViewRepresentable {
|
||||||
svgName.removeLast(4)
|
svgName.removeLast(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_ = UIView(svgNamed: svgName) { svgLayer in
|
_ = UIView(svgNamed: svgName) { svgLayer in
|
||||||
svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color
|
svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color
|
||||||
svgLayer.resizeToFit(hammock.frame)
|
svgLayer.resizeToFit(hammock.frame)
|
||||||
|
@ -1505,6 +1532,7 @@ struct SettingsSection<Content: View>: View {
|
||||||
|
|
||||||
struct SettingsCard<Content: View>: View {
|
struct SettingsCard<Content: View>: View {
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@AppStorage("oldSettingsUI") var oldSettingsUI = false
|
||||||
let content: Content
|
let content: Content
|
||||||
|
|
||||||
init(@ViewBuilder content: () -> Content) {
|
init(@ViewBuilder content: () -> Content) {
|
||||||
|
@ -1512,7 +1540,7 @@ struct SettingsCard<Content: View>: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone || oldSettingsUI {
|
||||||
content
|
content
|
||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
|
@ -1538,9 +1566,10 @@ struct SettingsToggle: View {
|
||||||
let label: String
|
let label: String
|
||||||
var disabled: Bool = false
|
var disabled: Bool = false
|
||||||
@AppStorage("toggleGreen") var toggleGreen: Bool = false
|
@AppStorage("toggleGreen") var toggleGreen: Bool = false
|
||||||
|
@AppStorage("oldSettingsUI") var oldSettingsUI = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
if UIDevice.current.userInterfaceIdiom == .phone || oldSettingsUI {
|
||||||
Toggle(isOn: $isOn) {
|
Toggle(isOn: $isOn) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if icon.hasSuffix(".svg") {
|
if icon.hasSuffix(".svg") {
|
||||||
|
|
|
@ -40,40 +40,61 @@ struct MeloNXApp: App {
|
||||||
|
|
||||||
@AppStorage("autoJIT") var autoJIT = false
|
@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 {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
if finishedStorage {
|
Group {
|
||||||
ContentView()
|
if finishedStorage {
|
||||||
.withFileImporter()
|
ContentView()
|
||||||
.onAppear {
|
.withFileImporter()
|
||||||
if checkForUpdate {
|
.onAppear {
|
||||||
checkLatestVersion()
|
if checkForUpdate {
|
||||||
|
checkLatestVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
print(metalHudEnabler.canMetalHud)
|
||||||
|
|
||||||
|
UserDefaults.standard.set(false, forKey: "lockInApp")
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: Binding(
|
||||||
print(metalHudEnabler.canMetalHud)
|
get: { showOutOfDateSheet && updateInfo != nil },
|
||||||
|
set: { newValue in
|
||||||
UserDefaults.standard.set(false, forKey: "lockInApp")
|
if !newValue {
|
||||||
}
|
showOutOfDateSheet = false
|
||||||
.sheet(isPresented: Binding(
|
updateInfo = nil
|
||||||
get: { showOutOfDateSheet && updateInfo != nil },
|
}
|
||||||
set: { newValue in
|
}
|
||||||
if !newValue {
|
)) {
|
||||||
showOutOfDateSheet = false
|
if let updateInfo = updateInfo {
|
||||||
updateInfo = nil
|
MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)) {
|
} else {
|
||||||
if let updateInfo = updateInfo {
|
SetupView(finished: $finished)
|
||||||
MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet)
|
.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()
|
task.resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func changeAppUI(_ string: String) -> String? {
|
||||||
|
guard let data = Data(base64Encoded: string) else { return nil }
|
||||||
|
return String(data: data, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 203 KiB |
21
src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/Contents.json
vendored
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
src/MeloNX/MeloNX/Assets/Assets.xcassets/DarkMode.imageset/darker.png
vendored
Normal file
After Width: | Height: | Size: 203 KiB |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 24 KiB |
21
src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/Contents.json
vendored
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelAppIcon.imageset/MeloNX 1024.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 24 KiB |
21
src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/Contents.json
vendored
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
src/MeloNX/MeloNX/Assets/Assets.xcassets/PixelRoundAppIcon.imageset/PixelPomeloNX 1024.png
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 383 KiB |
21
src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/Contents.json
vendored
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
src/MeloNX/MeloNX/Assets/Assets.xcassets/RoundAppIcon.imageset/copycat.png
vendored
Normal file
After Width: | Height: | Size: 383 KiB |
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 269 KiB |
21
src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/Contents.json
vendored
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
src/MeloNX/MeloNX/Assets/Assets.xcassets/uwuAppIcon.imageset/melowonx.png
vendored
Normal file
After Width: | Height: | Size: 269 KiB |
|
@ -10,7 +10,7 @@
|
||||||
</data>
|
</data>
|
||||||
<key>Info.plist</key>
|
<key>Info.plist</key>
|
||||||
<data>
|
<data>
|
||||||
UOH9NuuEcz5NQiQlrM2LNFaG2pI=
|
RTwvCLsTMs+YfZ9ZeF25QYe7/LE=
|
||||||
</data>
|
</data>
|
||||||
<key>Modules/module.modulemap</key>
|
<key>Modules/module.modulemap</key>
|
||||||
<data>
|
<data>
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>files</key>
|
<key>files</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>.DS_Store</key>
|
||||||
|
<data>
|
||||||
|
7Mfr8shT4pXWBr/plN+uNkIabdM=
|
||||||
|
</data>
|
||||||
<key>Headers/StosJIT-Swift.h</key>
|
<key>Headers/StosJIT-Swift.h</key>
|
||||||
<data>
|
<data>
|
||||||
h9vaTwhC6FlnyKmIkaxLQGlFd1g=
|
h9vaTwhC6FlnyKmIkaxLQGlFd1g=
|
||||||
|
@ -26,7 +30,7 @@
|
||||||
</data>
|
</data>
|
||||||
<key>Modules/StosJIT.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo</key>
|
<key>Modules/StosJIT.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo</key>
|
||||||
<data>
|
<data>
|
||||||
2mJoWBgg56N+3OxKfIDMLZFNHVk=
|
nihJghwM5m7kxkQD7UvrWyHkLy8=
|
||||||
</data>
|
</data>
|
||||||
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json</key>
|
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json</key>
|
||||||
<data>
|
<data>
|
||||||
|
@ -79,7 +83,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>hash2</key>
|
<key>hash2</key>
|
||||||
<data>
|
<data>
|
||||||
sZBe57nozztJzv83RPLjKIRYGSQmeE7XYCqr63xZONM=
|
+Ehvco7cQbAaF7zufvBYTiGXFp37Hjym/Pav514sGPk=
|
||||||
</data>
|
</data>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json</key>
|
<key>Modules/StosJIT.swiftmodule/arm64-apple-ios.abi.json</key>
|
||||||
|
|
|
@ -159,7 +159,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void RunCommand(Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer)
|
public static void RunCommand(Span<byte> 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.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,9 +31,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
_device = device;
|
_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;
|
nuint structureOffset = 0;
|
||||||
|
|
||||||
for (int seg = 0; seg < segments.Length; seg++)
|
for (int seg = 0; seg < segments.Length; seg++)
|
||||||
|
@ -42,45 +49,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
int binding = segment.Binding;
|
int binding = segment.Binding;
|
||||||
int count = segment.Count;
|
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(),
|
stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>();
|
||||||
DstBinding = (uint)binding,
|
}
|
||||||
DescriptorCount = (uint)count,
|
else if (IsBufferTextureType(segment.Type))
|
||||||
|
{
|
||||||
|
stride = (nuint)Unsafe.SizeOf<BufferView>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[entryIndex] = new DescriptorUpdateTemplateEntry()
|
||||||
|
{
|
||||||
|
DescriptorType = descriptorType,
|
||||||
|
DstBinding = (uint)(binding + i),
|
||||||
|
DescriptorCount = 1, // Always 1 descriptor per entry
|
||||||
Offset = structureOffset,
|
Offset = structureOffset,
|
||||||
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
|
Stride = stride
|
||||||
};
|
};
|
||||||
|
|
||||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
|
structureOffset += stride;
|
||||||
}
|
entryIndex++;
|
||||||
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<BufferView>()
|
|
||||||
};
|
|
||||||
|
|
||||||
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
entries[seg] = new DescriptorUpdateTemplateEntry()
|
|
||||||
{
|
|
||||||
DescriptorType = segment.Type.Convert(),
|
|
||||||
DstBinding = (uint)binding,
|
|
||||||
DescriptorCount = (uint)count,
|
|
||||||
Offset = structureOffset,
|
|
||||||
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
|
|
||||||
};
|
|
||||||
|
|
||||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +87,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var info = new DescriptorUpdateTemplateCreateInfo()
|
var info = new DescriptorUpdateTemplateCreateInfo()
|
||||||
{
|
{
|
||||||
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
|
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
|
||||||
DescriptorUpdateEntryCount = (uint)segments.Length,
|
DescriptorUpdateEntryCount = (uint)totalDescriptors,
|
||||||
PDescriptorUpdateEntries = entries,
|
PDescriptorUpdateEntries = entries,
|
||||||
|
|
||||||
TemplateType = DescriptorUpdateTemplateType.DescriptorSet,
|
TemplateType = DescriptorUpdateTemplateType.DescriptorSet,
|
||||||
|
@ -124,23 +122,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
int entry = 0;
|
int entry = 0;
|
||||||
nuint structureOffset = 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<DescriptorBufferInfo>()
|
|
||||||
};
|
|
||||||
|
|
||||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
|
|
||||||
}
|
|
||||||
|
|
||||||
int startBinding = 0;
|
|
||||||
int bindingCount = 0;
|
|
||||||
|
|
||||||
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
|
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < descriptor.Count; i++)
|
for (int i = 0; i < descriptor.Count; i++)
|
||||||
|
@ -149,28 +130,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if ((updateMask & (1L << binding)) != 0)
|
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<DescriptorBufferInfo>()
|
||||||
|
};
|
||||||
|
|
||||||
bindingCount = 0;
|
structureOffset += (nuint)Unsafe.SizeOf<DescriptorBufferInfo>();
|
||||||
}
|
entry++;
|
||||||
|
|
||||||
if (bindingCount == 0)
|
|
||||||
{
|
|
||||||
startBinding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindingCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindingCount > 0)
|
|
||||||
{
|
|
||||||
AddBinding(startBinding, bindingCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
Size = (int)structureOffset;
|
Size = (int)structureOffset;
|
||||||
|
|
||||||
var info = new DescriptorUpdateTemplateCreateInfo()
|
var info = new DescriptorUpdateTemplateCreateInfo()
|
||||||
|
|
|
@ -3,10 +3,10 @@ using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Buffer = Silk.NET.Vulkan.Buffer;
|
||||||
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
|
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
|
||||||
using Format = Ryujinx.Graphics.GAL.Format;
|
using Format = Ryujinx.Graphics.GAL.Format;
|
||||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||||
|
@ -141,11 +141,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
|
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
|
||||||
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
|
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
|
||||||
|
|
||||||
_textureArrayRefs = Array.Empty<ArrayRef<TextureArray>>();
|
_textureArrayRefs = [];
|
||||||
_imageArrayRefs = Array.Empty<ArrayRef<ImageArray>>();
|
_imageArrayRefs = [];
|
||||||
|
|
||||||
_textureArrayExtraRefs = Array.Empty<ArrayRef<TextureArray>>();
|
_textureArrayExtraRefs = [];
|
||||||
_imageArrayExtraRefs = Array.Empty<ArrayRef<ImageArray>>();
|
_imageArrayExtraRefs = [];
|
||||||
|
|
||||||
_uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings];
|
_uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings];
|
||||||
_storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings];
|
_storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings];
|
||||||
|
@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
_uniformSetPd = new int[Constants.MaxUniformBufferBindings];
|
_uniformSetPd = new int[Constants.MaxUniformBufferBindings];
|
||||||
|
|
||||||
var initialImageInfo = new DescriptorImageInfo
|
DescriptorImageInfo initialImageInfo = new()
|
||||||
{
|
{
|
||||||
ImageLayout = ImageLayout.General,
|
ImageLayout = ImageLayout.General,
|
||||||
};
|
};
|
||||||
|
@ -217,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if (isMainPipeline)
|
if (isMainPipeline)
|
||||||
{
|
{
|
||||||
FeedbackLoopHazards = new();
|
FeedbackLoopHazards = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
// Check stage bindings
|
// Check stage bindings
|
||||||
|
|
||||||
_uniformMirrored.Union(_uniformSet).SignalSet((int binding, int count) =>
|
_uniformMirrored.Union(_uniformSet).SignalSet((binding, count) =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
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++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
|
@ -301,13 +301,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
for (int i = 0; i < segment.Count; i++)
|
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);
|
texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ref var arrayRef = ref _textureArrayRefs[segment.Binding];
|
ref ArrayRef<TextureArray> arrayRef = ref _textureArrayRefs[segment.Binding];
|
||||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||||
}
|
}
|
||||||
|
@ -322,13 +322,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
for (int i = 0; i < segment.Count; i++)
|
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);
|
image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ref var arrayRef = ref _imageArrayRefs[segment.Binding];
|
ref ArrayRef<ImageArray> arrayRef = ref _imageArrayRefs[segment.Binding];
|
||||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||||
}
|
}
|
||||||
|
@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++)
|
for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++)
|
||||||
{
|
{
|
||||||
var bindingSegments = _program.BindingSegments[setIndex];
|
ResourceBindingSegment[] bindingSegments = _program.BindingSegments[setIndex];
|
||||||
|
|
||||||
if (bindingSegments.Length == 0)
|
if (bindingSegments.Length == 0)
|
||||||
{
|
{
|
||||||
|
@ -348,18 +348,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if (segment.IsArray)
|
if (segment.IsArray)
|
||||||
{
|
{
|
||||||
if (segment.Type == ResourceType.Texture ||
|
if (segment.Type is ResourceType.Texture or
|
||||||
segment.Type == ResourceType.Sampler ||
|
ResourceType.Sampler or
|
||||||
segment.Type == ResourceType.TextureAndSampler ||
|
ResourceType.TextureAndSampler or
|
||||||
segment.Type == ResourceType.BufferTexture)
|
ResourceType.BufferTexture)
|
||||||
{
|
{
|
||||||
ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
|
ref ArrayRef<TextureArray> arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
|
||||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
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<ImageArray> arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
|
||||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||||
}
|
}
|
||||||
|
@ -424,8 +424,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
for (int i = 0; i < buffers.Length; i++)
|
for (int i = 0; i < buffers.Length; i++)
|
||||||
{
|
{
|
||||||
var assignment = buffers[i];
|
BufferAssignment assignment = buffers[i];
|
||||||
var buffer = assignment.Range;
|
BufferRange buffer = assignment.Range;
|
||||||
int index = assignment.Binding;
|
int index = assignment.Binding;
|
||||||
|
|
||||||
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
|
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
|
||||||
|
@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Range = (ulong)buffer.Size,
|
Range = (ulong)buffer.Size,
|
||||||
};
|
};
|
||||||
|
|
||||||
var newRef = new BufferRef(vkBuffer, ref buffer);
|
BufferRef newRef = new(vkBuffer, ref buffer);
|
||||||
|
|
||||||
ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
|
ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
|
||||||
|
|
||||||
|
@ -460,7 +460,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
for (int i = 0; i < buffers.Length; i++)
|
for (int i = 0; i < buffers.Length; i++)
|
||||||
{
|
{
|
||||||
var vkBuffer = buffers[i];
|
Auto<DisposableBuffer> vkBuffer = buffers[i];
|
||||||
int index = first + i;
|
int index = first + i;
|
||||||
|
|
||||||
ref BufferRef currentBufferRef = ref _storageBufferRefs[index];
|
ref BufferRef currentBufferRef = ref _storageBufferRefs[index];
|
||||||
|
@ -633,8 +633,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
for (int i = 0; i < buffers.Length; i++)
|
for (int i = 0; i < buffers.Length; i++)
|
||||||
{
|
{
|
||||||
var assignment = buffers[i];
|
BufferAssignment assignment = buffers[i];
|
||||||
var buffer = assignment.Range;
|
BufferRange buffer = assignment.Range;
|
||||||
int index = assignment.Binding;
|
int index = assignment.Binding;
|
||||||
|
|
||||||
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
|
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
|
||||||
|
@ -678,7 +678,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var program = _program;
|
ShaderCollection program = _program;
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.Uniform))
|
if (_dirty.HasFlag(DirtyFlags.Uniform))
|
||||||
{
|
{
|
||||||
|
@ -699,13 +699,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if (_dirty.HasFlag(DirtyFlags.Texture))
|
if (_dirty.HasFlag(DirtyFlags.Texture))
|
||||||
{
|
{
|
||||||
if (program.UpdateTexturesWithoutTemplate)
|
if (false)
|
||||||
{
|
{
|
||||||
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
|
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
|
||||||
}
|
}
|
||||||
else
|
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;
|
return mirrored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void UpdateAndBind(CommandBufferScoped cbs, ShaderCollection program, int setIndex, PipelineBindPoint pbp)
|
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)
|
if (bindingSegments.Length == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dummyBuffer = _dummyBuffer?.GetBuffer();
|
Auto<DisposableBuffer> dummyBuffer = _dummyBuffer?.GetBuffer();
|
||||||
|
|
||||||
if (_updateDescriptorCacheCbIndex)
|
if (_updateDescriptorCacheCbIndex)
|
||||||
{
|
{
|
||||||
|
@ -776,7 +781,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
|
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)
|
if (!program.HasMinimalLayout)
|
||||||
{
|
{
|
||||||
|
@ -811,12 +816,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split buffer updates into individual slices for MoltenVK compatibility
|
|
||||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
tu.Push(uniformBuffers.Slice(binding, count));
|
||||||
tu.Push(uniformBuffers.Slice(binding + i, 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (setIndex == PipelineBase.StorageSetIndex)
|
else if (setIndex == PipelineBase.StorageSetIndex)
|
||||||
{
|
{
|
||||||
|
@ -828,7 +830,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if (_storageSet.Set(index))
|
if (_storageSet.Set(index))
|
||||||
{
|
{
|
||||||
ref var info = ref _storageBuffers[index];
|
ref DescriptorBufferInfo info = ref _storageBuffers[index];
|
||||||
|
|
||||||
bool mirrored = UpdateBuffer(cbs,
|
bool mirrored = UpdateBuffer(cbs,
|
||||||
ref info,
|
ref info,
|
||||||
|
@ -840,12 +842,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split buffer updates into individual slices for MoltenVK compatibility
|
|
||||||
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
|
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
tu.Push(storageBuffers.Slice(binding, count));
|
||||||
tu.Push(storageBuffers.Slice(binding + i, 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (setIndex == PipelineBase.TextureSetIndex)
|
else if (setIndex == PipelineBase.TextureSetIndex)
|
||||||
{
|
{
|
||||||
|
@ -857,8 +856,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
ref var texture = ref textures[i];
|
ref DescriptorImageInfo texture = ref textures[i];
|
||||||
ref var refs = ref _textureRefs[binding + i];
|
ref TextureRef refs = ref _textureRefs[binding + i];
|
||||||
|
|
||||||
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
||||||
texture.Sampler = refs.Sampler?.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<DescriptorImageInfo>(textures[..count]);
|
||||||
{
|
|
||||||
tu.Push<DescriptorImageInfo>(textures.Slice(i, 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -888,10 +884,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
|
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
tu.Push<BufferView>(bufferTextures[..count]);
|
||||||
{
|
|
||||||
tu.Push<BufferView>(bufferTextures.Slice(i, 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -919,10 +912,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
|
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
tu.Push<DescriptorImageInfo>(images[..count]);
|
||||||
{
|
|
||||||
tu.Push<DescriptorImageInfo>(images.Slice(i, 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -933,10 +923,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, true) ?? default;
|
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, true) ?? default;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
tu.Push<BufferView>(bufferImages[..count]);
|
||||||
{
|
|
||||||
tu.Push<BufferView>(bufferImages.Slice(i, 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -953,31 +940,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sets = dsc.GetSets();
|
DescriptorSet[] sets = dsc.GetSets();
|
||||||
_templateUpdater.Commit(_gd, _device, sets[0]);
|
_templateUpdater.Commit(_gd, _device, sets[0]);
|
||||||
|
|
||||||
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
|
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
|
||||||
{
|
{
|
||||||
int setIndex = PipelineBase.TextureSetIndex;
|
int setIndex = PipelineBase.TextureSetIndex;
|
||||||
var bindingSegments = program.BindingSegments[setIndex];
|
ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex];
|
||||||
|
|
||||||
if (bindingSegments.Length == 0)
|
if (bindingSegments.Length == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dummyImageInfo = new DescriptorImageInfo
|
if (_updateDescriptorCacheCbIndex)
|
||||||
{
|
{
|
||||||
ImageView = _dummyTexture.GetImageView().Get(cbs).Value,
|
_updateDescriptorCacheCbIndex = false;
|
||||||
Sampler = _dummySampler.GetSampler().Get(cbs).Value,
|
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
|
||||||
ImageLayout = ImageLayout.General
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
|
DescriptorSetCollection dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
|
||||||
|
|
||||||
foreach (ResourceBindingSegment segment in bindingSegments)
|
foreach (ResourceBindingSegment segment in bindingSegments)
|
||||||
{
|
{
|
||||||
|
@ -988,78 +973,56 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
if (segment.Type != ResourceType.BufferTexture)
|
if (segment.Type != ResourceType.BufferTexture)
|
||||||
{
|
{
|
||||||
|
Span<DescriptorImageInfo> textures = _textures;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
int index = binding + i;
|
ref DescriptorImageInfo texture = ref textures[i];
|
||||||
ref var textureRef = ref _textureRefs[index];
|
ref TextureRef refs = ref _textureRefs[binding + i];
|
||||||
|
|
||||||
var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
|
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
|
||||||
var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler;
|
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
|
||||||
|
|
||||||
var imageInfo = new DescriptorImageInfo
|
if (texture.ImageView.Handle == 0)
|
||||||
{
|
{
|
||||||
ImageView = imageView.Handle != 0 ? imageView : dummyImageInfo.ImageView,
|
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
|
||||||
Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler,
|
}
|
||||||
ImageLayout = ImageLayout.General
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
|
Span<BufferView> bufferTextures = _bufferTextures;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
int index = binding + i;
|
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
|
||||||
var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default;
|
|
||||||
dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var arrayRef = _textureArrayRefs[binding];
|
|
||||||
|
|
||||||
if (segment.Type != ResourceType.BufferTexture)
|
if (segment.Type != ResourceType.BufferTexture)
|
||||||
{
|
{
|
||||||
var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler);
|
dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var bufferViews = arrayRef.Array.GetBufferViews(cbs);
|
dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sets = dsc.GetSets();
|
DescriptorSet[] sets = dsc.GetSets();
|
||||||
|
|
||||||
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,8 +1030,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs)
|
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs)
|
||||||
{
|
{
|
||||||
int sequence = _pdSequence;
|
int sequence = _pdSequence;
|
||||||
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
|
ResourceBindingSegment[] bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
|
||||||
var dummyBuffer = _dummyBuffer?.GetBuffer();
|
Auto<DisposableBuffer> dummyBuffer = _dummyBuffer?.GetBuffer();
|
||||||
|
|
||||||
long updatedBindings = 0;
|
long updatedBindings = 0;
|
||||||
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
|
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
|
||||||
|
@ -1115,12 +1078,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
|
private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
|
||||||
{
|
{
|
||||||
// We don't support clearing texture descriptors currently.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default;
|
Buffer dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default;
|
||||||
|
|
||||||
foreach (ResourceBindingSegment segment in _program.ClearSegments[setIndex])
|
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++)
|
for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < program.BindingSegments.Length; setIndex++)
|
||||||
{
|
{
|
||||||
var bindingSegments = program.BindingSegments[setIndex];
|
ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex];
|
||||||
|
|
||||||
if (bindingSegments.Length == 0)
|
if (bindingSegments.Length == 0)
|
||||||
{
|
{
|
||||||
|
@ -1145,10 +1108,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
DescriptorSet[] sets = null;
|
DescriptorSet[] sets = null;
|
||||||
|
|
||||||
if (segment.Type == ResourceType.Texture ||
|
if (segment.Type is ResourceType.Texture or
|
||||||
segment.Type == ResourceType.Sampler ||
|
ResourceType.Sampler or
|
||||||
segment.Type == ResourceType.TextureAndSampler ||
|
ResourceType.TextureAndSampler or
|
||||||
segment.Type == ResourceType.BufferTexture)
|
ResourceType.BufferTexture)
|
||||||
{
|
{
|
||||||
sets = _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets(
|
sets = _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets(
|
||||||
_device,
|
_device,
|
||||||
|
@ -1159,7 +1122,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_dummyTexture,
|
_dummyTexture,
|
||||||
_dummySampler);
|
_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(
|
sets = _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets(
|
||||||
_device,
|
_device,
|
||||||
|
@ -1230,4 +1193,4 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -186,20 +186,18 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return sets;
|
return sets;
|
||||||
}
|
}
|
||||||
|
|
||||||
DescriptorSetTemplate template = program.Templates[setIndex];
|
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
|
||||||
|
|
||||||
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
|
|
||||||
|
|
||||||
if (!_isBuffer)
|
if (!_isBuffer)
|
||||||
{
|
{
|
||||||
tu.Push(GetImageInfos(_gd, cbs, dummyTexture));
|
dsc.UpdateImages(0, 0, GetImageInfos(_gd, cbs, dummyTexture), DescriptorType.StorageImage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tu.Push(GetBufferViews(cbs));
|
dsc.UpdateBufferImages(0, 0, GetBufferViews(cbs), DescriptorType.StorageTexelBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
templateUpdater.Commit(_gd, device, sets[0]);
|
sets = dsc.GetSets();
|
||||||
|
|
||||||
return sets;
|
return sets;
|
||||||
}
|
}
|
||||||
|
|
|
@ -785,7 +785,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
||||||
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
||||||
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
|
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
|
||||||
gatherBiasPrecision: (int)Capabilities.SubTexelPrecisionBits, //IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0,
|
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0,
|
||||||
maximumGpuMemory: GetTotalGPUMemory());
|
maximumGpuMemory: GetTotalGPUMemory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,25 +21,25 @@ namespace Ryujinx.HLE
|
||||||
/// The virtual file system used by the FS service.
|
/// The virtual file system used by the FS service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly VirtualFileSystem VirtualFileSystem;
|
public readonly VirtualFileSystem VirtualFileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The manager for handling a LibHac Horizon instance.
|
/// The manager for handling a LibHac Horizon instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly LibHacHorizonManager LibHacHorizonManager;
|
public readonly LibHacHorizonManager LibHacHorizonManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The account manager used by the account service.
|
/// The account manager used by the account service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly AccountManager AccountManager;
|
public readonly AccountManager AccountManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The content manager used by the NCM service.
|
/// The content manager used by the NCM service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly ContentManager ContentManager;
|
public readonly ContentManager ContentManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The persistent information between run for multi-application capabilities.
|
/// 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.
|
/// The GPU renderer to use for all GPU operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly IRenderer GpuRenderer;
|
public readonly IRenderer GpuRenderer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The audio device driver to use for all audio operations.
|
/// The audio device driver to use for all audio operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly IHardwareDeviceDriver AudioDeviceDriver;
|
public readonly IHardwareDeviceDriver AudioDeviceDriver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The handler for various UI related operations needed outside of HLE.
|
/// The handler for various UI related operations needed outside of HLE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly IHostUIHandler HostUIHandler;
|
public readonly IHostUIHandler HostUIHandler;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the memory configuration used by the emulation context.
|
/// Control the memory configuration used by the emulation context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly MemoryConfiguration MemoryConfiguration;
|
public readonly MemoryConfiguration MemoryConfiguration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The system language to use in the settings service.
|
/// The system language to use in the settings service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly SystemLanguage SystemLanguage;
|
public readonly SystemLanguage SystemLanguage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The system region to use in the settings service.
|
/// The system region to use in the settings service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly RegionCode Region;
|
public readonly RegionCode Region;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the vertical sync in the SurfaceFlinger service.
|
/// Control the initial state of the vertical sync in the SurfaceFlinger service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnableVsync;
|
public readonly bool EnableVsync;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the docked mode.
|
/// Control the initial state of the docked mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnableDockedMode;
|
public readonly bool EnableDockedMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control if the Profiled Translation Cache (PTC) should be used.
|
/// Control if the Profiled Translation Cache (PTC) should be used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnablePtc;
|
public readonly bool EnablePtc;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control if the guest application should be told that there is a Internet connection available.
|
/// Control if the guest application should be told that there is a Internet connection available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableInternetAccess { internal get; set; }
|
public bool EnableInternetAccess;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control LibHac's integrity check level.
|
/// Control LibHac's integrity check level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly IntegrityCheckLevel FsIntegrityCheckLevel;
|
public readonly IntegrityCheckLevel FsIntegrityCheckLevel;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control LibHac's global access logging level. Value must be between 0 and 3.
|
/// Control LibHac's global access logging level. Value must be between 0 and 3.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly int FsGlobalAccessLogMode;
|
public readonly int FsGlobalAccessLogMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The system time offset to apply to the time service steady and local clocks.
|
/// The system time offset to apply to the time service steady and local clocks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly long SystemTimeOffset;
|
public readonly long SystemTimeOffset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The system timezone used by the time service.
|
/// The system timezone used by the time service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal readonly string TimeZone;
|
public readonly string TimeZone;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Type of the memory manager used on CPU emulation.
|
/// Type of the memory manager used on CPU emulation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MemoryManagerMode MemoryManagerMode { internal get; set; }
|
public MemoryManagerMode MemoryManagerMode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the ignore missing services setting.
|
/// 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.
|
/// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// TODO: Update this again.
|
/// TODO: Update this again.
|
||||||
public bool IgnoreMissingServices { internal get; set; }
|
public bool IgnoreMissingServices { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aspect Ratio applied to the renderer window by the SurfaceFlinger service.
|
/// Aspect Ratio applied to the renderer window by the SurfaceFlinger service.
|
||||||
|
@ -152,22 +152,22 @@ namespace Ryujinx.HLE
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Use Hypervisor over JIT if available.
|
/// Use Hypervisor over JIT if available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool UseHypervisor;
|
public readonly bool UseHypervisor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiplayer LAN Interface ID (device GUID)
|
/// Multiplayer LAN Interface ID (device GUID)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string MultiplayerLanInterfaceId { internal get; set; }
|
public string MultiplayerLanInterfaceId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiplayer Mode
|
/// Multiplayer Mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiplayerMode MultiplayerMode { internal get; set; }
|
public MultiplayerMode MultiplayerMode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action RefreshInputConfig { internal get; set; }
|
public Action RefreshInputConfig { get; set; }
|
||||||
|
|
||||||
public HLEConfiguration(VirtualFileSystem virtualFileSystem,
|
public HLEConfiguration(VirtualFileSystem virtualFileSystem,
|
||||||
LibHacHorizonManager libHacHorizonManager,
|
LibHacHorizonManager libHacHorizonManager,
|
||||||
|
|
|
@ -396,7 +396,7 @@ namespace Ryujinx.Headless.SDL2
|
||||||
[UnmanagedCallersOnly(EntryPoint = "pause_emulation")]
|
[UnmanagedCallersOnly(EntryPoint = "pause_emulation")]
|
||||||
public static void PauseEmulation(bool shouldPause)
|
public static void PauseEmulation(bool shouldPause)
|
||||||
{
|
{
|
||||||
if (_window != null)
|
if (_window != null && _window.Device != null)
|
||||||
{
|
{
|
||||||
if (!shouldPause)
|
if (!shouldPause)
|
||||||
{
|
{
|
||||||
|
@ -1721,5 +1721,137 @@ namespace Ryujinx.Headless.SDL2
|
||||||
span.Clear();
|
span.Clear();
|
||||||
Encoding.UTF8.GetBytes(source, span);
|
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<Options>(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("<Configuration>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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
private string _gpuDriverName;
|
private string _gpuDriverName;
|
||||||
|
|
||||||
private readonly AspectRatio _aspectRatio;
|
public AspectRatio _aspectRatio;
|
||||||
private readonly bool _enableMouse;
|
private readonly bool _enableMouse;
|
||||||
|
|
||||||
public WindowBase(
|
public WindowBase(
|
||||||
|
@ -162,7 +162,7 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
private void InitializeWindow()
|
private void InitializeWindow()
|
||||||
{
|
{
|
||||||
if (this is Ryujinx.Headless.SDL2.Vulkan.MoltenVKWindow) {
|
if (this is Vulkan.MoltenVKWindow) {
|
||||||
string message = $"Not using SDL Windows, Skipping...";
|
string message = $"Not using SDL Windows, Skipping...";
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, message);
|
Logger.Info?.Print(LogClass.Application, message);
|
||||||
|
|