forked from MeloNX/MeloNX
Rewrite the menu Code, Add Metal HUD to advanced options and more
This commit is contained in:
parent
93af19e200
commit
a6b4f2d91f
@ -32,6 +32,13 @@
|
|||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
4E2953AB2D803BC9000497CD /* 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 */;
|
||||||
@ -46,13 +53,6 @@
|
|||||||
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
remoteGlobalIDString = 4E80A98C2CD6F54500029585;
|
||||||
remoteInfo = MeloNX;
|
remoteInfo = MeloNX;
|
||||||
};
|
};
|
||||||
4EE019E62D7CF7D600B7D583 /* 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 = (
|
||||||
4EE019E72D7CF7D600B7D583 /* PBXTargetDependency */,
|
4E2953AC2D803BC9000497CD /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
@ -362,7 +362,7 @@
|
|||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1620;
|
LastSwiftUpdateCheck = 1620;
|
||||||
LastUpgradeCheck = 1610;
|
LastUpgradeCheck = 1620;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
4E80A98C2CD6F54500029585 = {
|
4E80A98C2CD6F54500029585 = {
|
||||||
CreatedOnToolsVersion = 16.1;
|
CreatedOnToolsVersion = 16.1;
|
||||||
@ -453,7 +453,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
|
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/publish/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
@ -482,6 +482,11 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
4E2953AC2D803BC9000497CD /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
|
||||||
|
targetProxy = 4E2953AB2D803BC9000497CD /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
@ -492,11 +497,6 @@
|
|||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
targetProxy = 4E80A9A82CD6F54700029585 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
4EE019E72D7CF7D600B7D583 /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
|
|
||||||
targetProxy = 4EE019E62D7CF7D600B7D583 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
|
BD43C6262D1B249E003BBC42 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
|
target = BD43C61D2D1B23AB003BBC42 /* Ryujinx */;
|
||||||
@ -708,8 +708,12 @@
|
|||||||
"$(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 = fast;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
||||||
@ -827,6 +831,17 @@
|
|||||||
"$(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",
|
||||||
|
"$(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;
|
||||||
@ -905,8 +920,12 @@
|
|||||||
"$(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 = fast;
|
GCC_OPTIMIZATION_LEVEL = z;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
||||||
@ -1024,6 +1043,17 @@
|
|||||||
"$(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",
|
||||||
|
"$(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;
|
||||||
|
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1610"
|
LastUpgradeVersion = "1620"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@ -64,7 +64,6 @@
|
|||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
enableGPUValidationMode = "1"
|
||||||
showGraphicsOverview = "Yes"
|
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
<ActionContent
|
<ActionContent
|
||||||
title = "Run Script"
|
title = "Run Script"
|
||||||
scriptText = "REPO_DIR="$(cd "${SRCROOT}/../../" && pwd)" SCRIPT_PATH="$REPO_DIR/distribution/ios/get_dotnet.sh" sh "${SCRIPT_PATH}" "
|
scriptText = "REPO_DIR="$(cd "${SRCROOT}/../../" && pwd)" SCRIPT_PATH="$REPO_DIR/distribution/ios/get_dotnet.sh" echo "Xcode is located at: $DEVELOPER_DIR" sh "${SCRIPT_PATH}" "
|
||||||
shellToInvoke = "/bin/bash">
|
shellToInvoke = "/bin/bash">
|
||||||
<EnvironmentBuildable>
|
<EnvironmentBuildable>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
|
@ -19,12 +19,13 @@ func enableJITEB() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
showLaunchAppAlert(jsonData: data!, in: UIApplication.shared.windows.last!.rootViewController!)
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
let lastWindow = windowScene.windows.last {
|
||||||
|
showLaunchAppAlert(jsonData: data!, in: lastWindow.rootViewController!)
|
||||||
|
} else {
|
||||||
|
fatalError("Unable to get Window")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -78,8 +78,6 @@ class NativeController: Hashable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a game controller for the virtual joystick
|
|
||||||
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
|
||||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
if controller == nil {
|
if controller == nil {
|
||||||
|
@ -70,8 +70,6 @@ class VirtualController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a game controller for the virtual joystick
|
|
||||||
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
|
||||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
if controller == nil {
|
if controller == nil {
|
||||||
|
@ -10,9 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
class MTLHud {
|
class MTLHud {
|
||||||
|
|
||||||
var canMetalHud: Bool {
|
@Published var canMetalHud: Bool = false
|
||||||
return openMetalDylib()
|
|
||||||
}
|
|
||||||
|
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
if let getenv = getenv("MTL_HUD_ENABLED") {
|
if let getenv = getenv("MTL_HUD_ENABLED") {
|
||||||
@ -24,7 +22,17 @@ class MTLHud {
|
|||||||
static let shared = MTLHud()
|
static let shared = MTLHud()
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
openMetalDylib()
|
let _ = openMetalDylib() // i'm fixing the warnings just because you said i suck at coding Autumn (propenchiefer,
|
||||||
|
https://youtu.be/tc65SNOTMz4 7:23)
|
||||||
|
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||||
|
enable()
|
||||||
|
} else {
|
||||||
|
disable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggle() {
|
||||||
|
print(UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED"))
|
||||||
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||||
enable()
|
enable()
|
||||||
} else {
|
} else {
|
||||||
@ -35,16 +43,15 @@ class MTLHud {
|
|||||||
func openMetalDylib() -> Bool {
|
func openMetalDylib() -> Bool {
|
||||||
let path = "/usr/lib/libMTLHud.dylib"
|
let path = "/usr/lib/libMTLHud.dylib"
|
||||||
|
|
||||||
// Load the dynamic library
|
|
||||||
if dlopen(path, RTLD_NOW) != nil {
|
if dlopen(path, RTLD_NOW) != nil {
|
||||||
// Library loaded successfully
|
|
||||||
print("Library loaded from \(path)")
|
print("Library loaded from \(path)")
|
||||||
|
canMetalHud = true
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
// Handle error
|
|
||||||
if let error = String(validatingUTF8: dlerror()) {
|
if let error = String(validatingUTF8: dlerror()) {
|
||||||
print("Error loading library: \(error)")
|
print("Error loading library: \(error)")
|
||||||
}
|
}
|
||||||
|
canMetalHud = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,7 +360,6 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchFirmwareVersion() -> String {
|
func fetchFirmwareVersion() -> String {
|
||||||
do {
|
|
||||||
let firmwareVersionPointer = installed_firmware_version()
|
let firmwareVersionPointer = installed_firmware_version()
|
||||||
if let pointer = firmwareVersionPointer {
|
if let pointer = firmwareVersionPointer {
|
||||||
let firmwareVersion = String(cString: pointer)
|
let firmwareVersion = String(cString: pointer)
|
||||||
@ -370,10 +369,6 @@ class Ryujinx {
|
|||||||
return firmwareVersion
|
return firmwareVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "0"
|
return "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,62 +496,6 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func repeatuntilfindLayer() {
|
|
||||||
Task { @MainActor in
|
|
||||||
while self.metalLayer == nil {
|
|
||||||
let layer = self.getMetalLayer(nil)
|
|
||||||
|
|
||||||
if layer != nil {
|
|
||||||
self.metalLayer = layer
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(forTimeInterval: 0.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
|
|
||||||
var window = window
|
|
||||||
if window == nil {
|
|
||||||
window = SDL_GetWindowFromID(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var windowInfo = SDL_SysWMinfo()
|
|
||||||
SDL_GetWindowWMInfo(window, &windowInfo)
|
|
||||||
|
|
||||||
|
|
||||||
guard let uiWindow = windowInfo.info.uikit.window,
|
|
||||||
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
|
|
||||||
print("Unable to get root view")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findMetalLayer(in view: UIView) -> CAMetalLayer? {
|
|
||||||
if let metalLayer = view.layer as? CAMetalLayer {
|
|
||||||
return metalLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
for subview in view.subviews {
|
|
||||||
if let metalLayer = findMetalLayer(in: subview) {
|
|
||||||
return metalLayer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let existingLayer = findMetalLayer(in: rootView) {
|
|
||||||
print("Found Metal Layer")
|
|
||||||
return existingLayer
|
|
||||||
}
|
|
||||||
print("found nothing")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static func log(_ message: String) {
|
static func log(_ message: String) {
|
||||||
print("[Ryujinx] \(message)")
|
print("[Ryujinx] \(message)")
|
||||||
|
@ -32,7 +32,7 @@ struct LaunchGameIntentDef: AppIntent {
|
|||||||
|
|
||||||
let ryujinx = Ryujinx.shared.games
|
let ryujinx = Ryujinx.shared.games
|
||||||
|
|
||||||
let name = findClosestGameName(input: gameName, games: ryujinx.flatMap(\.titleName))
|
let name = findClosestGameName(input: gameName, games: ryujinx.compactMap(\.titleName))
|
||||||
|
|
||||||
let urlString = "melonx://game?name=\(name ?? gameName)"
|
let urlString = "melonx://game?name=\(name ?? gameName)"
|
||||||
print(urlString)
|
print(urlString)
|
||||||
|
@ -63,22 +63,15 @@ public struct Game: Identifiable, Equatable, Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
||||||
// Access the struct
|
|
||||||
let gameInfoValue = gameInfo
|
let gameInfoValue = gameInfo
|
||||||
|
|
||||||
// Get the image data
|
|
||||||
let imageSize = Int(gameInfoValue.ImageSize)
|
let imageSize = Int(gameInfoValue.ImageSize)
|
||||||
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
||||||
print("Invalid image size.")
|
print("Invalid image size.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the ImageData byte array to Swift's Data
|
|
||||||
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
||||||
|
|
||||||
// Create a UIImage (or NSImage on macOS)
|
|
||||||
print(imageData)
|
|
||||||
|
|
||||||
return UIImage(data: imageData)
|
return UIImage(data: imageData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift
Normal file
38
src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// LatestVersionResponse.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 12/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
struct LatestVersionResponse: Codable {
|
||||||
|
let version_number: String
|
||||||
|
let version_number_stripped: String
|
||||||
|
let changelog: String
|
||||||
|
let download_link: String
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
static let example1 = LatestVersionResponse(
|
||||||
|
version_number: "1.0.0",
|
||||||
|
version_number_stripped: "100",
|
||||||
|
changelog: """
|
||||||
|
- Rewrite Display Code (SDL isn't used for display anymore)
|
||||||
|
- Add New Onboarding / Setup
|
||||||
|
- Better Performance
|
||||||
|
- Remove "SDL Window" option in settings
|
||||||
|
- Fix JIT Cache Regions
|
||||||
|
- Fix how JIT is detected in Settings
|
||||||
|
- Fix ABYX being swapped on controller.
|
||||||
|
- Settings are now a config.json file
|
||||||
|
- Fix Performance Overlay not showing when Virtual Controller is hidden
|
||||||
|
- Add displaying logs when Loading or in-game
|
||||||
|
- Fix Launching games from outside of the roms folder
|
||||||
|
- Add Waiting for JIT popup
|
||||||
|
- Fix spesific Games
|
||||||
|
- Added Back Herobrine ("You were supposed to be the hero, Bryan")
|
||||||
|
""",
|
||||||
|
download_link: "https://example.com"
|
||||||
|
)
|
||||||
|
#endif
|
||||||
|
}
|
@ -6,12 +6,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
// import SDL2
|
|
||||||
import GameController
|
import GameController
|
||||||
import Darwin
|
import Darwin
|
||||||
import UIKit
|
import UIKit
|
||||||
import MetalKit
|
import MetalKit
|
||||||
// import SDL
|
|
||||||
|
|
||||||
struct MoltenVKSettings: Codable, Hashable {
|
struct MoltenVKSettings: Codable, Hashable {
|
||||||
let string: String
|
let string: String
|
||||||
@ -19,6 +17,8 @@ struct MoltenVKSettings: Codable, Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
// Games
|
// Games
|
||||||
@State private var game: Game?
|
@State private var game: Game?
|
||||||
|
|
||||||
@ -55,26 +55,25 @@ struct ContentView: View {
|
|||||||
@State var isLoading = true
|
@State var isLoading = true
|
||||||
@State var jitNotEnabled = false
|
@State var jitNotEnabled = false
|
||||||
|
|
||||||
|
// MARK: - SDL
|
||||||
|
var sdlInitFlags: UInt32 = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
init() {
|
init() {
|
||||||
var defaultConfig = loadSettings()
|
var defaultConfig = loadSettings()
|
||||||
if defaultConfig == nil {
|
if defaultConfig == nil {
|
||||||
saveSettings(config: .init(gamepath: ""))
|
saveSettings(config: .init(gamepath: ""))
|
||||||
|
|
||||||
defaultConfig = loadSettings()
|
defaultConfig = loadSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_config = State(initialValue: defaultConfig!)
|
_config = State(initialValue: defaultConfig!)
|
||||||
|
|
||||||
let defaultSettings: [MoltenVKSettings] = [ // Default MoltenVK Settings.
|
let defaultSettings: [MoltenVKSettings] = [
|
||||||
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
|
||||||
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_LOG_LEVEL", value: "0"),
|
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
|
MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "0"),
|
||||||
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "0"),
|
|
||||||
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64)
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
|
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -85,8 +84,18 @@ struct ContentView: View {
|
|||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if game != nil, !jitNotEnabled {
|
if game != nil && (!jitNotEnabled || ignoreJIT) {
|
||||||
// This is when the game starts to stop the animation
|
gameView
|
||||||
|
} else if game != nil && jitNotEnabled {
|
||||||
|
jitErrorView
|
||||||
|
} else {
|
||||||
|
mainMenuView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - View Components
|
||||||
|
|
||||||
|
private var gameView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if #available(iOS 16, *) {
|
if #available(iOS 16, *) {
|
||||||
EmulationView(startgame: $game)
|
EmulationView(startgame: $game)
|
||||||
@ -97,36 +106,16 @@ struct ContentView: View {
|
|||||||
|
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.black
|
Color.black.opacity(0.8)
|
||||||
.opacity(0.8)
|
emulationView.ignoresSafeArea(.all)
|
||||||
emulationView
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
}
|
}
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.ignoresSafeArea(.all)
|
.ignoresSafeArea(.all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if game != nil, ignoreJIT {
|
|
||||||
ZStack {
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
EmulationView(startgame: $game)
|
|
||||||
.persistentSystemOverlays(.hidden)
|
|
||||||
} else {
|
|
||||||
EmulationView(startgame: $game)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isLoading {
|
private var jitErrorView: some View {
|
||||||
ZStack {
|
|
||||||
Color.black
|
|
||||||
.opacity(0.8)
|
|
||||||
emulationView
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
}
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if game != nil {
|
|
||||||
Text("")
|
Text("")
|
||||||
.sheet(isPresented: $jitNotEnabled) {
|
.sheet(isPresented: $jitNotEnabled) {
|
||||||
JITPopover() {
|
JITPopover() {
|
||||||
@ -134,64 +123,76 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.interactiveDismissDisabled()
|
.interactiveDismissDisabled()
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// This is the main menu view that includes the Settings and the Game Selector
|
|
||||||
mainMenuView
|
private var mainMenuView: some View {
|
||||||
.onAppear() {
|
MainTabView(
|
||||||
|
startemu: $game,
|
||||||
|
config: $config,
|
||||||
|
MVKconfig: $settings,
|
||||||
|
controllersList: $controllersList,
|
||||||
|
currentControllers: $currentControllers,
|
||||||
|
onscreencontroller: $onscreencontroller
|
||||||
|
)
|
||||||
|
.onAppear {
|
||||||
quits = false
|
quits = false
|
||||||
|
let _ = loadSettings()
|
||||||
loadSettings()
|
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
initControllerObservers() // This initializes the Controller Observers that refreshes the controller list when a new controller connecvts.
|
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
|
||||||
}
|
|
||||||
.onOpenURL() { url in
|
|
||||||
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
|
||||||
components.host == "game" {
|
|
||||||
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
|
||||||
|
|
||||||
game = Ryujinx.shared.games.first(where: { $0.titleId == text })
|
|
||||||
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
|
||||||
game = Ryujinx.shared.games.first(where: { $0.titleName == text })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private func initControllerObservers() {
|
|
||||||
NotificationCenter.default.addObserver(
|
|
||||||
forName: .GCControllerDidConnect,
|
|
||||||
object: nil,
|
|
||||||
queue: .main) { notification in
|
|
||||||
if let controller = notification.object as? GCController {
|
|
||||||
print("Controller connected: \(controller.productCategory)")
|
|
||||||
nativeControllers[controller] = .init(controller)
|
|
||||||
refreshControllersList()
|
refreshControllersList()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(
|
print(MTLHud.shared.isEnabled)
|
||||||
forName: .GCControllerDidDisconnect,
|
|
||||||
object: nil,
|
initControllerObservers()
|
||||||
queue: .main) { notification in
|
|
||||||
if let controller = notification.object as? GCController {
|
Air.play(AnyView(
|
||||||
print("Controller disconnected: \(controller.productCategory)")
|
VStack {
|
||||||
nativeControllers[controller]?.cleanup()
|
Image(systemName: "gamecontroller")
|
||||||
nativeControllers[controller] = nil
|
.font(.system(size: 300))
|
||||||
refreshControllersList()
|
.foregroundColor(.gray)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
Text("Select Game")
|
||||||
|
.font(.system(size: 150))
|
||||||
|
.bold()
|
||||||
}
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
checkJitStatus()
|
||||||
|
}
|
||||||
|
.onOpenURL { url in
|
||||||
|
handleDeepLink(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - View Components
|
|
||||||
private var emulationView: some View {
|
private var emulationView: some View {
|
||||||
GeometryReader { screenGeometry in
|
GeometryReader { screenGeometry in
|
||||||
ZStack {
|
ZStack {
|
||||||
|
gameLoadingContent(screenGeometry: screenGeometry)
|
||||||
|
|
||||||
|
HStack{
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
if showlogsloading {
|
||||||
|
LogFileView(isfps: true)
|
||||||
|
.frame(alignment: .topLeading)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helper Methods
|
||||||
|
|
||||||
|
private func gameLoadingContent(screenGeometry: GeometryProxy) -> some View {
|
||||||
HStack(spacing: screenGeometry.size.width * 0.04) {
|
HStack(spacing: screenGeometry.size.width * 0.04) {
|
||||||
if let icon = game?.icon {
|
if let icon = game?.icon {
|
||||||
Image(uiImage: icon)
|
Image(uiImage: icon)
|
||||||
@ -209,6 +210,18 @@ struct ContentView: View {
|
|||||||
.font(.system(size: min(screenGeometry.size.width * 0.04, 32)))
|
.font(.system(size: min(screenGeometry.size.width * 0.04, 32)))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
loadingProgressBar(screenGeometry: screenGeometry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, screenGeometry.size.width * 0.06)
|
||||||
|
.padding(.vertical, screenGeometry.size.height * 0.05)
|
||||||
|
.position(
|
||||||
|
x: screenGeometry.size.width / 2,
|
||||||
|
y: screenGeometry.size.height * 0.5
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadingProgressBar(screenGeometry: GeometryProxy) -> some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
|
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
|
||||||
|
|
||||||
@ -234,20 +247,14 @@ struct ContentView: View {
|
|||||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
isAnimating = true
|
isAnimating = true
|
||||||
|
|
||||||
setupEmulation()
|
setupEmulation()
|
||||||
|
|
||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
||||||
if get_current_fps() != 0 {
|
if get_current_fps() != 0 {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
|
||||||
isAnimating = false
|
isAnimating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
timer.invalidate()
|
timer.invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,59 +263,42 @@ struct ContentView: View {
|
|||||||
.frame(height: min(screenGeometry.size.height * 0.015, 12))
|
.frame(height: min(screenGeometry.size.height * 0.015, 12))
|
||||||
.frame(width: min(screenGeometry.size.width * 0.35, 350))
|
.frame(width: min(screenGeometry.size.width * 0.35, 350))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.padding(.horizontal, screenGeometry.size.width * 0.06)
|
|
||||||
.padding(.vertical, screenGeometry.size.height * 0.05)
|
|
||||||
.position(
|
|
||||||
x: screenGeometry.size.width / 2,
|
|
||||||
y: screenGeometry.size.height * 0.5
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if showlogsloading {
|
|
||||||
LogFileView(isfps: true)
|
|
||||||
.frame(alignment: .topLeading)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mainMenuView: some View {
|
|
||||||
MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
|
|
||||||
.onAppear() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { timer in
|
|
||||||
refreshControllersList()
|
|
||||||
}
|
|
||||||
|
|
||||||
Air.play(AnyView(
|
|
||||||
VStack {
|
|
||||||
Image(systemName: "gamecontroller")
|
|
||||||
.font(.system(size: 300))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
Text("Select Game")
|
|
||||||
.font(.system(size: 150))
|
|
||||||
.bold()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
|
|
||||||
jitNotEnabled = !isJITEnabled()
|
|
||||||
if jitNotEnabled {
|
|
||||||
useTrollStore ? askForJIT() : jitStreamerEB ? enableJITEB() : print("no JIT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
|
||||||
var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; // Initialises SDL2 for Events, Game Controller, Joystick, Audio and Video.
|
|
||||||
private func initializeSDL() {
|
private func initializeSDL() {
|
||||||
setMoltenVKSettings()
|
setMoltenVKSettings()
|
||||||
SDL_SetMainReady() // Sets SDL Ready
|
SDL_SetMainReady()
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true
|
SDL_iPhoneSetEventPump(SDL_TRUE)
|
||||||
SDL_Init(SdlInitFlags) // Initialises SDL2
|
SDL_Init(sdlInitFlags)
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func initControllerObservers() {
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
forName: .GCControllerDidConnect,
|
||||||
|
object: nil,
|
||||||
|
queue: .main
|
||||||
|
) { notification in
|
||||||
|
if let controller = notification.object as? GCController {
|
||||||
|
print("Controller connected: \(controller.productCategory)")
|
||||||
|
nativeControllers[controller] = .init(controller)
|
||||||
|
refreshControllersList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
forName: .GCControllerDidDisconnect,
|
||||||
|
object: nil,
|
||||||
|
queue: .main
|
||||||
|
) { notification in
|
||||||
|
if let controller = notification.object as? GCController {
|
||||||
|
print("Controller disconnected: \(controller.productCategory)")
|
||||||
|
nativeControllers[controller]?.cleanup()
|
||||||
|
nativeControllers[controller] = nil
|
||||||
|
refreshControllersList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupEmulation() {
|
private func setupEmulation() {
|
||||||
isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
||||||
|
|
||||||
@ -330,8 +320,7 @@ struct ContentView: View {
|
|||||||
currentControllers = []
|
currentControllers = []
|
||||||
|
|
||||||
if controllersList.count == 1 {
|
if controllersList.count == 1 {
|
||||||
let controller = controllersList[0]
|
currentControllers.append(controllersList[0])
|
||||||
currentControllers.append(controller)
|
|
||||||
} else if (controllersList.count - 1) >= 1 {
|
} else if (controllersList.count - 1) >= 1 {
|
||||||
for controller in controllersList {
|
for controller in controllersList {
|
||||||
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
|
||||||
@ -341,29 +330,18 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private func start(displayid: UInt32) {
|
private func start(displayid: UInt32) {
|
||||||
guard let game else { return }
|
guard let game else { return }
|
||||||
|
|
||||||
config.gamepath = game.fileURL.path
|
config.gamepath = game.fileURL.path
|
||||||
config.inputids = Array(Set(currentControllers.map(\.id)))
|
config.inputids = Array(Set(currentControllers.map(\.id)))
|
||||||
|
|
||||||
if mVKPreFillBuffer {
|
configureEnvironmentVariables()
|
||||||
let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2")
|
|
||||||
setenv(setting.string, setting.value, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if syncqsubmits {
|
|
||||||
let setting = MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "2")
|
|
||||||
setenv(setting.string, setting.value, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.inputids.isEmpty {
|
if config.inputids.isEmpty {
|
||||||
config.inputids.append("0")
|
config.inputids.append("0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try Ryujinx.shared.start(with: config)
|
try Ryujinx.shared.start(with: config)
|
||||||
} catch {
|
} catch {
|
||||||
@ -371,14 +349,45 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func configureEnvironmentVariables() {
|
||||||
|
if mVKPreFillBuffer {
|
||||||
|
setenv("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", "2", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if syncqsubmits {
|
||||||
|
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "2", 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sets MoltenVK Environment Variables
|
|
||||||
private func setMoltenVKSettings() {
|
private func setMoltenVKSettings() {
|
||||||
settings.forEach { setting in
|
settings.forEach { setting in
|
||||||
setenv(setting.string, setting.value, 1)
|
setenv(setting.string, setting.value, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func checkJitStatus() {
|
||||||
|
jitNotEnabled = !isJITEnabled()
|
||||||
|
if jitNotEnabled {
|
||||||
|
if useTrollStore {
|
||||||
|
askForJIT()
|
||||||
|
} else if jitStreamerEB {
|
||||||
|
enableJITEB()
|
||||||
|
} else {
|
||||||
|
print("no JIT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleDeepLink(_ url: URL) {
|
||||||
|
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||||
|
components.host == "game" {
|
||||||
|
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
|
||||||
|
game = Ryujinx.shared.games.first(where: { $0.titleId == text })
|
||||||
|
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
|
||||||
|
game = Ryujinx.shared.games.first(where: { $0.titleName == text })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array {
|
extension Array {
|
||||||
|
@ -15,7 +15,7 @@ struct MetalView: UIViewRepresentable {
|
|||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
|
||||||
if Ryujinx.shared.emulationUIView == nil {
|
if Ryujinx.shared.emulationUIView == nil {
|
||||||
var view = MeloMTKView()
|
let view = MeloMTKView()
|
||||||
|
|
||||||
guard let metalLayer = view.layer as? CAMetalLayer else {
|
guard let metalLayer = view.layer as? CAMetalLayer else {
|
||||||
fatalError("[Swift] Error: MTKView's layer is not a CAMetalLayer")
|
fatalError("[Swift] Error: MTKView's layer is not a CAMetalLayer")
|
||||||
@ -34,13 +34,19 @@ struct MetalView: UIViewRepresentable {
|
|||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
let uiview = UIView()
|
if Double(UIDevice.current.systemVersion)! < 17.0 {
|
||||||
|
|
||||||
uiview.layer.addSublayer(Ryujinx.shared.metalLayer!)
|
let uiview = MTKView()
|
||||||
|
let layer = Ryujinx.shared.metalLayer!
|
||||||
|
|
||||||
uiview.contentScaleFactor = Ryujinx.shared.metalLayer!.contentsScale
|
layer.frame = uiview.bounds
|
||||||
|
|
||||||
|
uiview.layer.addSublayer(layer)
|
||||||
|
|
||||||
return uiview
|
return uiview
|
||||||
|
} else {
|
||||||
|
return Ryujinx.shared.emulationUIView!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_ uiView: UIView, context: Context) {
|
func updateUIView(_ uiView: UIView, context: Context) {
|
||||||
|
@ -10,7 +10,7 @@ import MetalKit
|
|||||||
|
|
||||||
struct TouchView: UIViewRepresentable {
|
struct TouchView: UIViewRepresentable {
|
||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
var view = MeloMTKView()
|
let view = MeloMTKView()
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ struct GameLibraryView: View {
|
|||||||
let fileExtension = (url.pathExtension as NSString).utf8String
|
let fileExtension = (url.pathExtension as NSString).utf8String
|
||||||
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
|
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
|
||||||
|
|
||||||
var gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
|
let gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
|
||||||
|
|
||||||
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: url)
|
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: url)
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ struct SettingsView: View {
|
|||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
Toggle(isOn: $performacehud) {
|
Toggle(isOn: $performacehud) {
|
||||||
labelWithIcon("Performance Overlay", iconName: "speedometer")
|
labelWithIcon("Custom Performance Overlay", iconName: "speedometer")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
} header: {
|
} header: {
|
||||||
@ -452,7 +452,8 @@ struct SettingsView: View {
|
|||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
let mainWindow = windowScene.windows.last {
|
||||||
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
||||||
|
|
||||||
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
||||||
@ -481,7 +482,8 @@ struct SettingsView: View {
|
|||||||
}.tint(.blue)
|
}.tint(.blue)
|
||||||
.contextMenu() {
|
.contextMenu() {
|
||||||
Button {
|
Button {
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
let mainWindow = windowScene.windows.last {
|
||||||
let alertController = UIAlertController(title: "About MVK: Synchronous Queue Submits", message: "Enable this option if Mario Kart 8 is crashing at Grand Prix mode.", preferredStyle: .alert)
|
let alertController = UIAlertController(title: "About MVK: Synchronous Queue Submits", message: "Enable this option if Mario Kart 8 is crashing at Grand Prix mode.", preferredStyle: .alert)
|
||||||
|
|
||||||
let doneButton = UIAlertAction(title: "OK", style: .cancel, handler: nil)
|
let doneButton = UIAlertAction(title: "OK", style: .cancel, handler: nil)
|
||||||
@ -546,7 +548,7 @@ struct SettingsView: View {
|
|||||||
if ProcessInfo.processInfo.isiOSAppOnMac {
|
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
labelWithIcon("Memory: \(String(format: "%.0f GB", Double(totalMemory) / (1024 * 1024 * 1024)))", iconName: "memorychip.fill")
|
labelWithIcon("Memory: \(String(format: "%.0f GB", Double(totalMemory) / (1024 * 1024 * 1024)))", iconName: "memorychip.fill")
|
||||||
} else {
|
} else {
|
||||||
labelWithIcon("Device Memory: \(String(format: "%.0f GB", Double(totalMemory) / (1024 * 1024 * 1024)))", iconName: "memorychip.fill")
|
labelWithIcon("Device Memory: \(String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000))", iconName: "memorychip.fill")
|
||||||
}
|
}
|
||||||
|
|
||||||
labelWithIcon("\(deviceType) \(UIDevice.current.systemVersion)", iconName: "applelogo")
|
labelWithIcon("\(deviceType) \(UIDevice.current.systemVersion)", iconName: "applelogo")
|
||||||
@ -577,7 +579,16 @@ struct SettingsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(Int(getpagesize())))")
|
Text("\(String(Int(getpagesize())))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if MTLHud.shared.canMetalHud {
|
||||||
|
Toggle(isOn: $metalHUDEnabled) {
|
||||||
|
labelWithIcon("Metal Performance HUD", iconName: "speedometer")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.onChange(of: metalHUDEnabled) { newValue in
|
||||||
|
MTLHud.shared.toggle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle(isOn: $ignoreJIT) {
|
Toggle(isOn: $ignoreJIT) {
|
||||||
@ -617,7 +628,7 @@ struct SettingsView: View {
|
|||||||
.textCase(nil)
|
.textCase(nil)
|
||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). \n \n\(gamepo ? "the cake is a lie" : "")")
|
Text("For advanced users. See page size or add custom arguments for experimental features, \"Metal Performance HUD\" is not needed if you have it enabled in settings. \n \n\(gamepo ? "the cake is a lie" : "")")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// MeloNXUpdateSheet.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 and Bella on 12/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MeloNXUpdateSheet: View {
|
||||||
|
let updateInfo: LatestVersionResponse
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
iOSNav {
|
||||||
|
VStack {
|
||||||
|
Text("Version \(updateInfo.version_number) is available. You are currently on Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown").")
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
Text("Changelog:")
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
Text(updateInfo.changelog)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.frame(maxHeight: 400)
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
}
|
||||||
|
.padding(.top, 15)
|
||||||
|
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Button(action: {
|
||||||
|
if let url = URL(string: updateInfo.download_link) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Download Now")
|
||||||
|
.font(.title3)
|
||||||
|
.bold()
|
||||||
|
.frame(width: 300, height: 40)
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.frame(alignment: .bottom)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.navigationTitle("Version \(updateInfo.version_number) Available!")
|
||||||
|
.toolbar {
|
||||||
|
Button(action: {
|
||||||
|
isPresented = false
|
||||||
|
}) {
|
||||||
|
Text("Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,16 +18,32 @@ struct MeloNXApp: App {
|
|||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) var scenePhase
|
||||||
@State var alert: UIAlertController? = nil
|
@State var alert: UIAlertController? = nil
|
||||||
|
|
||||||
|
@State var showOutOfDateSheet = false
|
||||||
|
@State var updateInfo: LatestVersionResponse? = nil
|
||||||
|
|
||||||
@State var finished = false
|
@State var finished = false
|
||||||
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ZStack {
|
|
||||||
if showed || DRM != 1 {
|
|
||||||
|
|
||||||
if finishedStorage {
|
if finishedStorage {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
.onAppear {
|
||||||
|
checkLatestVersion()
|
||||||
|
}
|
||||||
|
.sheet(isPresented: Binding(
|
||||||
|
get: { showOutOfDateSheet && updateInfo != nil },
|
||||||
|
set: { newValue in
|
||||||
|
if !newValue {
|
||||||
|
showOutOfDateSheet = false
|
||||||
|
updateInfo = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
if let updateInfo = updateInfo {
|
||||||
|
MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SetupView(finished: $finished)
|
SetupView(finished: $finished)
|
||||||
.onChange(of: finished) { newValue in
|
.onChange(of: finished) { newValue in
|
||||||
@ -38,241 +54,50 @@ struct MeloNXApp: App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Group {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("Loading...")
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text(UIDevice.current.identifierForVendor?.uuidString ?? "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
initR()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.background(Color.black.opacity(1))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initR() {
|
func checkLatestVersion() {
|
||||||
if DRM == 1 {
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||||
DispatchQueue.main.async { [self] in
|
let strippedAppVersion = appVersion.replacingOccurrences(of: ".", with: "")
|
||||||
// drmcheck()
|
|
||||||
InitializeRyujinx() { bool in
|
#if DEBUG
|
||||||
if bool {
|
let urlString = "http://192.168.178.116:8000/api/latest_release"
|
||||||
print("Ryujinx Files Initialized Successfully")
|
#else
|
||||||
DispatchQueue.main.async { [self] in
|
let urlString = "https://melonx.org/api/latest_release"
|
||||||
withAnimation {
|
#endif
|
||||||
showed = true
|
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
print("Invalid URL")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
InitializeRyujinx() { bool in
|
|
||||||
if !bool, (scenePhase != .background || scenePhase == .inactive) {
|
|
||||||
withAnimation {
|
|
||||||
showed = false
|
|
||||||
}
|
|
||||||
if !(alert?.isViewLoaded ?? false) {
|
|
||||||
alert = showDMCAAlert()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
alert?.dismiss(animated: true)
|
|
||||||
showed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
showDMCAAlert()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func showAlert() -> UIAlertController? {
|
|
||||||
// Create the alert controller
|
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
|
||||||
let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert)
|
|
||||||
|
|
||||||
// Add a text field to the alert
|
|
||||||
alertController.addTextField { textField in
|
|
||||||
textField.placeholder = "Enter key here"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the "OK" action
|
|
||||||
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
|
||||||
// Get the text entered in the text field
|
|
||||||
if let textField = alertController.textFields?.first, let enteredText = textField.text {
|
|
||||||
print("Entered text: \(enteredText)")
|
|
||||||
UserDefaults.standard.set(enteredText, forKey: "MeloDRMID")
|
|
||||||
// drmcheck() { bool in
|
|
||||||
// if bool {
|
|
||||||
// showed = true
|
|
||||||
// } else {
|
|
||||||
// exit(0)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alertController.addAction(okAction)
|
|
||||||
|
|
||||||
// Add a "Cancel" action
|
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
|
||||||
alertController.addAction(cancelAction)
|
|
||||||
|
|
||||||
// Present the alert
|
|
||||||
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
|
||||||
|
|
||||||
return alertController
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDMCAAlert() -> UIAlertController? {
|
|
||||||
if let mainWindow = UIApplication.shared.windows.first {
|
|
||||||
let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return alertController
|
|
||||||
} else {
|
|
||||||
// uhoh
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func drmcheck(completion: @escaping (Bool) -> Void) {
|
|
||||||
if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() {
|
|
||||||
if let value = UserDefaults.standard.string(forKey: "MeloDRMID") {
|
|
||||||
if let url = URL(string: "https://mx.stossy11.com/auth/\(value)/\(base64device)") {
|
|
||||||
print(url)
|
|
||||||
// Create a URLSession
|
|
||||||
let session = URLSession.shared
|
|
||||||
|
|
||||||
// Create a data task
|
|
||||||
let task = session.dataTask(with: url) { data, response, error in
|
|
||||||
// Handle errors
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
exit(0)
|
print("Error checking for new version: \(error)")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check response and data
|
guard let data = data else {
|
||||||
if let response = response as? HTTPURLResponse, response.statusCode == 200 {
|
print("No data received")
|
||||||
print("Successfully Recieved API Data")
|
return
|
||||||
completion(true)
|
}
|
||||||
} else if let response = response as? HTTPURLResponse, response.statusCode == 201 {
|
|
||||||
print("Successfully Created Auth UUID")
|
do {
|
||||||
completion(true)
|
let latestVersionResponse = try JSONDecoder().decode(LatestVersionResponse.self, from: data)
|
||||||
} else {
|
let latestAPIVersionStripped = latestVersionResponse.version_number_stripped
|
||||||
completion(false)
|
|
||||||
|
if Int(strippedAppVersion) ?? 0 > Int(latestAPIVersionStripped) ?? 0 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
updateInfo = latestVersionResponse
|
||||||
|
showOutOfDateSheet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Failed to decode response: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the task
|
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func InitializeRyujinx(completion: @escaping (Bool) -> Void) {
|
|
||||||
let path = "aHR0cHM6Ly9teC5zdG9zc3kxMS5jb20v"
|
|
||||||
|
|
||||||
guard let value = Bundle.main.object(forInfoDictionaryKey: "MeloID") as? String, !value.isEmpty else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (detectRoms(path: path) != value) {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
let configuration = URLSessionConfiguration.default
|
|
||||||
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
||||||
configuration.urlCache = nil
|
|
||||||
|
|
||||||
let session = URLSession(configuration: configuration)
|
|
||||||
|
|
||||||
guard let url = URL(string: addFolders(path)!) else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let task = session.dataTask(with: url) { data, response, error in
|
|
||||||
if error != nil {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpResponse.statusCode == 200 {
|
|
||||||
completion(true)
|
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
task.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectRoms(path string: String) -> String {
|
|
||||||
let inputData = Data(string.utf8)
|
|
||||||
let romHash = SHA256.hash(data: inputData)
|
|
||||||
return romHash.compactMap { String(format: "%02x", $0) }.joined()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func addFolders(_ folderPath: String) -> String? {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
if let data = Data(base64Encoded: folderPath),
|
|
||||||
let decodedString = String(data: data, encoding: .utf8), let fileURL = UIDevice.current.identifierForVendor?.uuidString {
|
|
||||||
return decodedString + "auth/" + fileURL + "/"
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
|
|
||||||
func print() {
|
|
||||||
Swift.print(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,8 @@ struct SetupView: View {
|
|||||||
initialize()
|
initialize()
|
||||||
finished = false
|
finished = false
|
||||||
keysImported = Ryujinx.shared.checkIfKeysImported()
|
keysImported = Ryujinx.shared.checkIfKeysImported()
|
||||||
firmImported = (Ryujinx.shared.fetchFirmwareVersion() != "0")
|
print((Double(Ryujinx.shared.fetchFirmwareVersion()) ?? 0))
|
||||||
|
firmImported = ((Double(Ryujinx.shared.fetchFirmwareVersion()) ?? 0) != 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +117,9 @@ struct SetupView: View {
|
|||||||
.font(.title)
|
.font(.title)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
.onTapGesture(count: 2) {
|
||||||
|
showSkipAlert = true
|
||||||
|
}
|
||||||
|
|
||||||
Text("Set up your Nintendo Switch emulation environment by importing keys and firmware.")
|
Text("Set up your Nintendo Switch emulation environment by importing keys and firmware.")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
@ -365,8 +369,9 @@ struct SetupView: View {
|
|||||||
|
|
||||||
Ryujinx.shared.installFirmware(firmwarePath: fileURL.path)
|
Ryujinx.shared.installFirmware(firmwarePath: fileURL.path)
|
||||||
|
|
||||||
|
print(Double(Ryujinx.shared.fetchFirmwareVersion()) ?? 0)
|
||||||
|
|
||||||
firmImported = (Ryujinx.shared.fetchFirmwareVersion() != "0")
|
firmImported = ((Double(Ryujinx.shared.fetchFirmwareVersion()) ?? 0) != 0)
|
||||||
alertMessage = "Firmware installed successfully"
|
alertMessage = "Firmware installed successfully"
|
||||||
showAlert = true
|
showAlert = true
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -72,5 +72,6 @@ namespace Ryujinx.Common.Logging
|
|||||||
TamperMachine,
|
TamperMachine,
|
||||||
UI,
|
UI,
|
||||||
Vic,
|
Vic,
|
||||||
|
Memory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using Ryujinx.Memory;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Cpu.LightningJit.Cache
|
namespace Ryujinx.Cpu.LightningJit.Cache
|
||||||
{
|
{
|
||||||
@ -21,12 +22,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
private readonly ReservedRegion _region;
|
private readonly ReservedRegion _region;
|
||||||
private readonly CacheMemoryAllocator _cacheAllocator;
|
private readonly CacheMemoryAllocator _cacheAllocator;
|
||||||
|
public readonly IJitMemoryAllocator Allocator;
|
||||||
|
|
||||||
public CacheMemoryAllocator Allocator => _cacheAllocator;
|
public CacheMemoryAllocator CacheAllocator => _cacheAllocator;
|
||||||
public IntPtr Pointer => _region.Block.Pointer;
|
public IntPtr Pointer => _region.Block.Pointer;
|
||||||
|
|
||||||
public MemoryCache(IJitMemoryAllocator allocator, ulong size)
|
public MemoryCache(IJitMemoryAllocator allocator, ulong size)
|
||||||
{
|
{
|
||||||
|
Allocator = allocator;
|
||||||
_region = new(allocator, size);
|
_region = new(allocator, size);
|
||||||
_cacheAllocator = new((int)size);
|
_cacheAllocator = new((int)size);
|
||||||
}
|
}
|
||||||
@ -101,9 +104,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
|
|
||||||
private readonly IStackWalker _stackWalker;
|
private readonly IStackWalker _stackWalker;
|
||||||
private readonly Translator _translator;
|
private readonly Translator _translator;
|
||||||
private readonly MemoryCache _sharedCache;
|
private readonly List<MemoryCache> _sharedCaches;
|
||||||
private readonly MemoryCache _localCache;
|
private readonly List<MemoryCache> _localCaches;
|
||||||
private readonly PageAlignedRangeList _pendingMap;
|
private readonly Dictionary<ulong, PageAlignedRangeList> _pendingMaps;
|
||||||
private readonly object _lock;
|
private readonly object _lock;
|
||||||
|
|
||||||
class ThreadLocalCacheEntry
|
class ThreadLocalCacheEntry
|
||||||
@ -111,13 +114,15 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
public readonly int Offset;
|
public readonly int Offset;
|
||||||
public readonly int Size;
|
public readonly int Size;
|
||||||
public readonly IntPtr FuncPtr;
|
public readonly IntPtr FuncPtr;
|
||||||
|
public readonly int CacheIndex;
|
||||||
private int _useCount;
|
private int _useCount;
|
||||||
|
|
||||||
public ThreadLocalCacheEntry(int offset, int size, IntPtr funcPtr)
|
public ThreadLocalCacheEntry(int offset, int size, IntPtr funcPtr, int cacheIndex)
|
||||||
{
|
{
|
||||||
Offset = offset;
|
Offset = offset;
|
||||||
Size = size;
|
Size = size;
|
||||||
FuncPtr = funcPtr;
|
FuncPtr = funcPtr;
|
||||||
|
CacheIndex = cacheIndex;
|
||||||
_useCount = 0;
|
_useCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,12 +139,87 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
_stackWalker = stackWalker;
|
_stackWalker = stackWalker;
|
||||||
_translator = translator;
|
_translator = translator;
|
||||||
_sharedCache = new(allocator, SharedCacheSize);
|
_sharedCaches = new List<MemoryCache> { new(allocator, SharedCacheSize) };
|
||||||
_localCache = new(allocator, LocalCacheSize);
|
_localCaches = new List<MemoryCache> { new(allocator, LocalCacheSize) };
|
||||||
_pendingMap = new(_sharedCache.ReprotectAsRx, RegisterFunction);
|
_pendingMaps = new Dictionary<ulong, PageAlignedRangeList>();
|
||||||
_lock = new();
|
_lock = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PageAlignedRangeList GetPendingMapForCache(int cacheIndex)
|
||||||
|
{
|
||||||
|
ulong cacheKey = (ulong)cacheIndex;
|
||||||
|
if (!_pendingMaps.TryGetValue(cacheKey, out var pendingMap))
|
||||||
|
{
|
||||||
|
pendingMap = new PageAlignedRangeList(
|
||||||
|
(offset, size) => _sharedCaches[cacheIndex].ReprotectAsRx(offset, size),
|
||||||
|
(address, func) => RegisterFunction(address, func));
|
||||||
|
_pendingMaps[cacheKey] = pendingMap;
|
||||||
|
}
|
||||||
|
return pendingMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasInAnyPendingMap(ulong guestAddress)
|
||||||
|
{
|
||||||
|
foreach (var pendingMap in _pendingMaps.Values)
|
||||||
|
{
|
||||||
|
if (pendingMap.Has(guestAddress))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AllocateInSharedCache(int codeLength)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _sharedCaches.Count; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (i << 28) | _sharedCaches[i].Allocate(codeLength);
|
||||||
|
}
|
||||||
|
catch (OutOfMemoryException)
|
||||||
|
{
|
||||||
|
// Try next cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All existing caches are full, create a new one
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var allocator = _sharedCaches[0].Allocator;
|
||||||
|
_sharedCaches.Add(new(allocator, SharedCacheSize));
|
||||||
|
return (_sharedCaches.Count - 1) << 28 | _sharedCaches[_sharedCaches.Count - 1].Allocate(codeLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AllocateInLocalCache(int codeLength)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _localCaches.Count; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (i << 28) | _localCaches[i].Allocate(codeLength);
|
||||||
|
}
|
||||||
|
catch (OutOfMemoryException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var allocator = _localCaches[0].Allocator;
|
||||||
|
_localCaches.Add(new(allocator, LocalCacheSize));
|
||||||
|
return (_localCaches.Count - 1) << 28 | _localCaches[_localCaches.Count - 1].Allocate(codeLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int cacheIndex, int offset) SplitCacheOffset(int combinedOffset)
|
||||||
|
{
|
||||||
|
return (combinedOffset >> 28, combinedOffset & 0xFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe IntPtr Map(IntPtr framePointer, ReadOnlySpan<byte> code, ulong guestAddress, ulong guestSize)
|
public unsafe IntPtr Map(IntPtr framePointer, ReadOnlySpan<byte> code, ulong guestAddress, ulong guestSize)
|
||||||
{
|
{
|
||||||
if (TryGetThreadLocalFunction(guestAddress, out IntPtr funcPtr))
|
if (TryGetThreadLocalFunction(guestAddress, out IntPtr funcPtr))
|
||||||
@ -149,16 +229,18 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (!_pendingMap.Has(guestAddress) && !_translator.Functions.ContainsKey(guestAddress))
|
if (!HasInAnyPendingMap(guestAddress) && !_translator.Functions.ContainsKey(guestAddress))
|
||||||
{
|
{
|
||||||
int funcOffset = _sharedCache.Allocate(code.Length);
|
int combinedOffset = AllocateInSharedCache(code.Length);
|
||||||
|
var (cacheIndex, funcOffset) = SplitCacheOffset(combinedOffset);
|
||||||
|
|
||||||
funcPtr = _sharedCache.Pointer + funcOffset;
|
MemoryCache cache = _sharedCaches[cacheIndex];
|
||||||
|
funcPtr = cache.Pointer + funcOffset;
|
||||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
||||||
|
|
||||||
TranslatedFunction function = new(funcPtr, guestSize);
|
TranslatedFunction function = new(funcPtr, guestSize);
|
||||||
|
|
||||||
_pendingMap.Add(funcOffset, code.Length, guestAddress, function);
|
GetPendingMapForCache(cacheIndex).Add(funcOffset, code.Length, guestAddress, function);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearThreadLocalCache(framePointer);
|
ClearThreadLocalCache(framePointer);
|
||||||
@ -171,18 +253,56 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
// Ensure we will get an aligned offset from the allocator.
|
int cacheIndex;
|
||||||
_pendingMap.Pad(_sharedCache.Allocator);
|
int funcOffset;
|
||||||
|
IntPtr mappedFuncPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
for (cacheIndex = 0; cacheIndex < _sharedCaches.Count; cacheIndex++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pendingMap = GetPendingMapForCache(cacheIndex);
|
||||||
|
|
||||||
|
pendingMap.Pad(_sharedCaches[cacheIndex].CacheAllocator);
|
||||||
|
|
||||||
int sizeAligned = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
|
int sizeAligned = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
|
||||||
int funcOffset = _sharedCache.Allocate(sizeAligned);
|
funcOffset = _sharedCaches[cacheIndex].Allocate(sizeAligned);
|
||||||
|
|
||||||
Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0);
|
Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0);
|
||||||
|
|
||||||
IntPtr funcPtr = _sharedCache.Pointer + funcOffset;
|
IntPtr funcPtr1 = _sharedCaches[cacheIndex].Pointer + funcOffset;
|
||||||
|
code.CopyTo(new Span<byte>((void*)funcPtr1, code.Length));
|
||||||
|
|
||||||
|
_sharedCaches[cacheIndex].ReprotectAsRx(funcOffset, sizeAligned);
|
||||||
|
|
||||||
|
return funcPtr1;
|
||||||
|
}
|
||||||
|
catch (OutOfMemoryException)
|
||||||
|
{
|
||||||
|
// Try next cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All existing caches are full, create a new one
|
||||||
|
var allocator = _sharedCaches[0].Allocator;
|
||||||
|
var newCache = new MemoryCache(allocator, SharedCacheSize);
|
||||||
|
_sharedCaches.Add(newCache);
|
||||||
|
cacheIndex = _sharedCaches.Count - 1;
|
||||||
|
|
||||||
|
var newPendingMap = GetPendingMapForCache(cacheIndex);
|
||||||
|
|
||||||
|
// Ensure we will get an aligned offset from the allocator.
|
||||||
|
newPendingMap.Pad(newCache.CacheAllocator);
|
||||||
|
|
||||||
|
int newSizeAligned = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
|
||||||
|
funcOffset = newCache.Allocate(newSizeAligned);
|
||||||
|
|
||||||
|
Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0);
|
||||||
|
|
||||||
|
IntPtr funcPtr = newCache.Pointer + funcOffset;
|
||||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
||||||
|
|
||||||
_sharedCache.ReprotectAsRx(funcOffset, sizeAligned);
|
newCache.ReprotectAsRx(funcOffset, newSizeAligned);
|
||||||
|
|
||||||
return funcPtr;
|
return funcPtr;
|
||||||
}
|
}
|
||||||
@ -200,7 +320,15 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_pendingMap.Pad(_sharedCache.Allocator);
|
foreach (var pendingMap in _pendingMaps.Values)
|
||||||
|
{
|
||||||
|
// Get the cache index from the pendingMap key
|
||||||
|
if (_pendingMaps.FirstOrDefault(x => x.Value == pendingMap).Key is ulong cacheIndex)
|
||||||
|
{
|
||||||
|
// Use the correct shared cache for padding based on the cache index
|
||||||
|
pendingMap.Pad(_sharedCaches[(int)cacheIndex].CacheAllocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,12 +352,36 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<ulong> callStack = _stackWalker.GetCallStack(
|
IntPtr[] cachePointers = new IntPtr[_localCaches.Count];
|
||||||
|
int[] cacheSizes = new int[_localCaches.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < _localCaches.Count; i++)
|
||||||
|
{
|
||||||
|
cachePointers[i] = _localCaches[i].Pointer;
|
||||||
|
cacheSizes[i] = LocalCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr[] sharedPointers = new IntPtr[_sharedCaches.Count];
|
||||||
|
int[] sharedSizes = new int[_sharedCaches.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < _sharedCaches.Count; i++)
|
||||||
|
{
|
||||||
|
sharedPointers[i] = _sharedCaches[i].Pointer;
|
||||||
|
sharedSizes[i] = SharedCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the arrays and pass each element to GetCallStack
|
||||||
|
IEnumerable<ulong> callStack = null;
|
||||||
|
for (int i = 0; i < _localCaches.Count; i++)
|
||||||
|
{
|
||||||
|
callStack = _stackWalker.GetCallStack(
|
||||||
framePointer,
|
framePointer,
|
||||||
_localCache.Pointer,
|
cachePointers[i], // Passing each individual cachePointer
|
||||||
LocalCacheSize,
|
cacheSizes[i], // Passing each individual cacheSize
|
||||||
_sharedCache.Pointer,
|
sharedPointers[i], // Passing each individual sharedPointer
|
||||||
SharedCacheSize);
|
sharedSizes[i] // Passing each individual sharedSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<(ulong, ThreadLocalCacheEntry)> toDelete = new();
|
List<(ulong, ThreadLocalCacheEntry)> toDelete = new();
|
||||||
|
|
||||||
@ -237,7 +389,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
// We only want to delete if the function is already on the shared cache,
|
// We only want to delete if the function is already on the shared cache,
|
||||||
// otherwise we will keep translating the same function over and over again.
|
// otherwise we will keep translating the same function over and over again.
|
||||||
bool canDelete = !_pendingMap.Has(address);
|
bool canDelete = !HasInAnyPendingMap(address);
|
||||||
if (!canDelete)
|
if (!canDelete)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -267,12 +419,14 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
_threadLocalCache.Remove(address);
|
_threadLocalCache.Remove(address);
|
||||||
|
|
||||||
int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize);
|
int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize);
|
||||||
|
var (cacheIndex, offset) = SplitCacheOffset(entry.Offset);
|
||||||
|
|
||||||
_localCache.Free(entry.Offset, sizeAligned);
|
_localCaches[cacheIndex].Free(offset, sizeAligned);
|
||||||
_localCache.ReprotectAsRw(entry.Offset, sizeAligned);
|
_localCaches[cacheIndex].ReprotectAsRw(offset, sizeAligned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ClearEntireThreadLocalCache()
|
public void ClearEntireThreadLocalCache()
|
||||||
{
|
{
|
||||||
// Thread is exiting, delete everything.
|
// Thread is exiting, delete everything.
|
||||||
@ -287,9 +441,10 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
foreach ((_, ThreadLocalCacheEntry entry) in _threadLocalCache)
|
foreach ((_, ThreadLocalCacheEntry entry) in _threadLocalCache)
|
||||||
{
|
{
|
||||||
int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize);
|
int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize);
|
||||||
|
var (cacheIndex, offset) = SplitCacheOffset(entry.Offset);
|
||||||
|
|
||||||
_localCache.Free(entry.Offset, sizeAligned);
|
_localCaches[cacheIndex].Free(offset, sizeAligned);
|
||||||
_localCache.ReprotectAsRw(entry.Offset, sizeAligned);
|
_localCaches[cacheIndex].ReprotectAsRw(offset, sizeAligned);
|
||||||
}
|
}
|
||||||
|
|
||||||
_threadLocalCache.Clear();
|
_threadLocalCache.Clear();
|
||||||
@ -299,16 +454,17 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
private unsafe IntPtr AddThreadLocalFunction(ReadOnlySpan<byte> code, ulong guestAddress)
|
private unsafe IntPtr AddThreadLocalFunction(ReadOnlySpan<byte> code, ulong guestAddress)
|
||||||
{
|
{
|
||||||
int alignedSize = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
|
int alignedSize = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize());
|
||||||
int funcOffset = _localCache.Allocate(alignedSize);
|
int combinedOffset = AllocateInLocalCache(alignedSize);
|
||||||
|
var (cacheIndex, funcOffset) = SplitCacheOffset(combinedOffset);
|
||||||
|
|
||||||
Debug.Assert((funcOffset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
|
Debug.Assert((funcOffset & (int)(MemoryBlock.GetPageSize() - 1)) == 0);
|
||||||
|
|
||||||
IntPtr funcPtr = _localCache.Pointer + funcOffset;
|
IntPtr funcPtr = _localCaches[cacheIndex].Pointer + funcOffset;
|
||||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
||||||
|
|
||||||
(_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr));
|
(_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr, cacheIndex));
|
||||||
|
|
||||||
_localCache.ReprotectAsRx(funcOffset, alignedSize);
|
_localCaches[cacheIndex].ReprotectAsRx(funcOffset, alignedSize);
|
||||||
|
|
||||||
return funcPtr;
|
return funcPtr;
|
||||||
}
|
}
|
||||||
@ -326,8 +482,18 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_localCache.Dispose();
|
foreach (var cache in _localCaches)
|
||||||
_sharedCache.Dispose();
|
{
|
||||||
|
cache.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cache in _sharedCaches)
|
||||||
|
{
|
||||||
|
cache.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_localCaches.Clear();
|
||||||
|
_sharedCaches.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,16 +769,77 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
|
private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
|
||||||
{
|
{
|
||||||
|
const int MaxChunkSize = 1024 * 1024 * 16; // 16MB chunks
|
||||||
|
|
||||||
int bufferDataLength = GetBufferDataLength(data.Length);
|
int bufferDataLength = GetBufferDataLength(data.Length);
|
||||||
|
|
||||||
using var bufferHolder = _gd.BufferManager.Create(_gd, bufferDataLength);
|
if (bufferDataLength <= MaxChunkSize)
|
||||||
|
{
|
||||||
|
ProcessChunk(data, layer, level, layers, levels, singleSlice, region);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Auto<DisposableImage> imageAuto = GetImage();
|
if (!region.HasValue && !singleSlice && layers > 1)
|
||||||
|
{
|
||||||
|
int layerSize = data.Length / layers;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
// Load texture data inline if the texture has been used on the current command buffer.
|
for (int i = 0; i < layers; i++)
|
||||||
|
{
|
||||||
|
int currentLayer = layer + i;
|
||||||
|
int currentLayerSize = Math.Min(layerSize, data.Length - offset);
|
||||||
|
var layerData = data.Slice(offset, currentLayerSize);
|
||||||
|
|
||||||
|
ProcessChunk(layerData, currentLayer, level, 1, levels, true);
|
||||||
|
offset += layerSize;
|
||||||
|
|
||||||
|
if (offset >= data.Length)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (region.HasValue)
|
||||||
|
{
|
||||||
|
var rect = region.Value;
|
||||||
|
int dataPerPixel = data.Length / (rect.Width * rect.Height);
|
||||||
|
int rowStride = rect.Width * dataPerPixel;
|
||||||
|
|
||||||
|
int rowsPerChunk = Math.Max(1, MaxChunkSize / rowStride);
|
||||||
|
int originalHeight = rect.Height;
|
||||||
|
int currentY = rect.Y;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (currentY < rect.Y + originalHeight)
|
||||||
|
{
|
||||||
|
int chunkHeight = Math.Min(rowsPerChunk, rect.Y + originalHeight - currentY);
|
||||||
|
var chunkRegion = new Rectangle<int>(rect.X, currentY, rect.Width, chunkHeight);
|
||||||
|
|
||||||
|
int chunkSize = chunkHeight * rowStride;
|
||||||
|
int safeChunkSize = Math.Min(chunkSize, data.Length - offset);
|
||||||
|
var chunkData = data.Slice(offset, safeChunkSize);
|
||||||
|
|
||||||
|
ProcessChunk(chunkData, layer, level, 1, 1, true, chunkRegion);
|
||||||
|
|
||||||
|
currentY += chunkHeight;
|
||||||
|
offset += chunkSize;
|
||||||
|
|
||||||
|
if (offset >= data.Length)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ProcessChunk(data, layer, level, layers, levels, singleSlice, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessChunk(ReadOnlySpan<byte> chunkData, int chunkLayer, int chunkLevel, int chunkLayers, int chunkLevels, bool chunkSingleSlice, Rectangle<int>? chunkRegion = null)
|
||||||
|
{
|
||||||
|
int chunkBufferLength = GetBufferDataLength(chunkData.Length);
|
||||||
|
|
||||||
|
using var bufferHolder = _gd.BufferManager.Create(_gd, chunkBufferLength);
|
||||||
|
|
||||||
|
using (var imageAuto = GetImage())
|
||||||
|
{
|
||||||
bool loadInline = Storage.HasCommandBufferDependency(_gd.PipelineInternal.CurrentCommandBuffer);
|
bool loadInline = Storage.HasCommandBufferDependency(_gd.PipelineInternal.CurrentCommandBuffer);
|
||||||
|
|
||||||
var cbs = loadInline ? _gd.PipelineInternal.CurrentCommandBuffer : _gd.PipelineInternal.GetPreloadCommandBuffer();
|
var cbs = loadInline ? _gd.PipelineInternal.CurrentCommandBuffer : _gd.PipelineInternal.GetPreloadCommandBuffer();
|
||||||
|
|
||||||
if (loadInline)
|
if (loadInline)
|
||||||
@ -786,29 +847,41 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_gd.PipelineInternal.EndRenderPass();
|
_gd.PipelineInternal.EndRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data);
|
CopyDataToBuffer(bufferHolder.GetDataStorage(0, chunkBufferLength), chunkData);
|
||||||
|
|
||||||
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
|
||||||
var image = imageAuto.Get(cbs).Value;
|
var image = imageAuto.Get(cbs).Value;
|
||||||
|
|
||||||
if (region.HasValue)
|
if (chunkRegion.HasValue)
|
||||||
{
|
{
|
||||||
CopyFromOrToBuffer(
|
CopyFromOrToBuffer(
|
||||||
cbs.CommandBuffer,
|
cbs.CommandBuffer,
|
||||||
buffer,
|
buffer,
|
||||||
image,
|
image,
|
||||||
bufferDataLength,
|
chunkBufferLength,
|
||||||
false,
|
false,
|
||||||
layer,
|
chunkLayer,
|
||||||
level,
|
chunkLevel,
|
||||||
region.Value.X,
|
chunkRegion.Value.X,
|
||||||
region.Value.Y,
|
chunkRegion.Value.Y,
|
||||||
region.Value.Width,
|
chunkRegion.Value.Width,
|
||||||
region.Value.Height);
|
chunkRegion.Value.Height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice);
|
CopyFromOrToBuffer(
|
||||||
|
cbs.CommandBuffer,
|
||||||
|
buffer,
|
||||||
|
image,
|
||||||
|
chunkBufferLength,
|
||||||
|
false,
|
||||||
|
chunkLayer,
|
||||||
|
chunkLevel,
|
||||||
|
chunkLayers,
|
||||||
|
chunkLevels,
|
||||||
|
chunkSingleSlice);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ namespace Ryujinx.HLE
|
|||||||
|
|
||||||
MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable
|
MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable
|
||||||
? MemoryAllocationFlags.Reserve
|
? MemoryAllocationFlags.Reserve
|
||||||
: MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable;
|
: MemoryAllocationFlags.Reserve; // | MemoryAllocationFlags.Mirrorable;
|
||||||
|
|
||||||
#pragma warning disable IDE0055 // Disable formatting
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
AudioDeviceDriver = AddAudioCompatLayers(Configuration.AudioDeviceDriver);
|
AudioDeviceDriver = AddAudioCompatLayers(Configuration.AudioDeviceDriver);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user