forked from MeloNX/MeloNX
Compare commits
6 Commits
4ffb0ff617
...
fd0ce75f67
Author | SHA1 | Date | |
---|---|---|---|
|
fd0ce75f67 | ||
|
0e80bd3d51 | ||
|
f95281899c | ||
802a8d7bae | |||
7277e1fa9b | |||
27312d4f31 |
@ -25,6 +25,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
@ -197,6 +198,7 @@
|
||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
|
||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
|
||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
|
||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -656,10 +658,19 @@
|
||||
"$(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 = fast;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||
@ -670,7 +681,7 @@
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -710,6 +721,22 @@
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||
@ -755,10 +782,19 @@
|
||||
"$(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 = fast;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
||||
INFOPLIST_KEY_GCSupportsGameMode = YES;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
|
||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||
@ -769,7 +805,7 @@
|
||||
INFOPLIST_KEY_UIRequiresFullScreen = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -809,6 +845,22 @@
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||
|
Binary file not shown.
@ -12,12 +12,12 @@
|
||||
<key>Ryujinx.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
@ -0,0 +1,58 @@
|
||||
//
|
||||
// EntitlementChecker.swift
|
||||
// MeloNX
|
||||
//
|
||||
// Created by Stossy11 on 15/02/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
typealias SecTaskRef = OpaquePointer
|
||||
|
||||
@_silgen_name("SecTaskCopyValueForEntitlement")
|
||||
func SecTaskCopyValueForEntitlement(
|
||||
_ task: SecTaskRef,
|
||||
_ entitlement: NSString,
|
||||
_ error: NSErrorPointer
|
||||
) -> CFTypeRef?
|
||||
|
||||
@_silgen_name("SecTaskCreateFromSelf")
|
||||
func SecTaskCreateFromSelf(
|
||||
_ allocator: CFAllocator?
|
||||
) -> SecTaskRef?
|
||||
|
||||
@_silgen_name("SecTaskCopyValuesForEntitlements")
|
||||
func SecTaskCopyValuesForEntitlements(
|
||||
_ task: SecTaskRef,
|
||||
_ entitlements: CFArray,
|
||||
_ error: UnsafeMutablePointer<Unmanaged<CFError>?>?
|
||||
) -> CFDictionary?
|
||||
|
||||
func checkAppEntitlements(_ ents: [String]) -> [String: Any]? {
|
||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
||||
print("Failed to create SecTask")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let entitlements = SecTaskCopyValuesForEntitlements(task, ents as CFArray, nil) else {
|
||||
print("Failed to get entitlements")
|
||||
return nil
|
||||
}
|
||||
|
||||
return entitlements as? [String: Any]
|
||||
}
|
||||
|
||||
func checkAppEntitlement(_ ent: String) -> Bool? {
|
||||
guard let task = SecTaskCreateFromSelf(nil) else {
|
||||
print("Failed to create SecTask")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let entitlements = SecTaskCopyValueForEntitlement(task, ent as NSString, nil) else {
|
||||
print("Failed to get entitlements")
|
||||
return nil
|
||||
}
|
||||
|
||||
return entitlements.boolValue != nil && entitlements.boolValue
|
||||
}
|
@ -43,6 +43,8 @@ int main_ryujinx_sdl(int argc, char **argv);
|
||||
|
||||
int get_current_fps();
|
||||
|
||||
void set_title_update(const char* titleIdPtr, const char* updatePathPtr);
|
||||
|
||||
void initialize();
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -0,0 +1,28 @@
|
||||
//
|
||||
// AspectRatio.swift
|
||||
// MeloNX
|
||||
//
|
||||
// Created by Stossy11 on 16/02/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum AspectRatio: String, Codable, CaseIterable {
|
||||
case fixed4x3 = "Fixed4x3"
|
||||
case fixed16x9 = "Fixed16x9"
|
||||
case fixed16x10 = "Fixed16x10"
|
||||
case fixed21x9 = "Fixed21x9"
|
||||
case fixed32x9 = "Fixed32x9"
|
||||
case stretched = "Stretched"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .fixed4x3: return "4:3"
|
||||
case .fixed16x9: return "16:9 (Default)"
|
||||
case .fixed16x10: return "16:10"
|
||||
case .fixed21x9: return "21:9"
|
||||
case .fixed32x9: return "32:9"
|
||||
case .stretched: return "Stretched (Full Screen)"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
//
|
||||
// Language.swift
|
||||
// MeloNX
|
||||
//
|
||||
// Created by Stossy11 on 16/02/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum SystemLanguage: String, Codable, CaseIterable {
|
||||
case japanese = "Japanese"
|
||||
case americanEnglish = "AmericanEnglish"
|
||||
case french = "French"
|
||||
case german = "German"
|
||||
case italian = "Italian"
|
||||
case spanish = "Spanish"
|
||||
case chinese = "Chinese"
|
||||
case korean = "Korean"
|
||||
case dutch = "Dutch"
|
||||
case portuguese = "Portuguese"
|
||||
case russian = "Russian"
|
||||
case taiwanese = "Taiwanese"
|
||||
case britishEnglish = "BritishEnglish"
|
||||
case canadianFrench = "CanadianFrench"
|
||||
case latinAmericanSpanish = "LatinAmericanSpanish"
|
||||
case simplifiedChinese = "SimplifiedChinese"
|
||||
case traditionalChinese = "TraditionalChinese"
|
||||
case brazilianPortuguese = "BrazilianPortuguese"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .japanese: return "Japanese"
|
||||
case .americanEnglish: return "American English"
|
||||
case .french: return "French"
|
||||
case .german: return "German"
|
||||
case .italian: return "Italian"
|
||||
case .spanish: return "Spanish"
|
||||
case .chinese: return "Chinese"
|
||||
case .korean: return "Korean"
|
||||
case .dutch: return "Dutch"
|
||||
case .portuguese: return "Portuguese"
|
||||
case .russian: return "Russian"
|
||||
case .taiwanese: return "Taiwanese"
|
||||
case .britishEnglish: return "British English"
|
||||
case .canadianFrench: return "Canadian French"
|
||||
case .latinAmericanSpanish: return "Latin American Spanish"
|
||||
case .simplifiedChinese: return "Simplified Chinese"
|
||||
case .traditionalChinese: return "Traditional Chinese"
|
||||
case .brazilianPortuguese: return "Brazilian Portuguese"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Region.swift
|
||||
// MeloNX
|
||||
//
|
||||
// Created by Stossy11 on 16/02/2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum SystemRegionCode: String, Codable, CaseIterable {
|
||||
case japan = "Japan"
|
||||
case usa = "USA"
|
||||
case europe = "Europe"
|
||||
case australia = "Australia"
|
||||
case china = "China"
|
||||
case korea = "Korea"
|
||||
case taiwan = "Taiwan"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .japan: return "Japan"
|
||||
case .usa: return "United States"
|
||||
case .europe: return "Europe"
|
||||
case .australia: return "Australia"
|
||||
case .china: return "China"
|
||||
case .korea: return "Korea"
|
||||
case .taiwan: return "Taiwan"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,26 +28,6 @@ struct iOSNav<Content: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
public enum AspectRatio: String, Codable, CaseIterable {
|
||||
case fixed4x3 = "Fixed4x3"
|
||||
case fixed16x9 = "Fixed16x9"
|
||||
case fixed16x10 = "Fixed16x10"
|
||||
case fixed21x9 = "Fixed21x9"
|
||||
case fixed32x9 = "Fixed32x9"
|
||||
case stretched = "Stretched"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .fixed4x3: return "4:3"
|
||||
case .fixed16x9: return "16:9 (Default)"
|
||||
case .fixed16x10: return "16:10"
|
||||
case .fixed21x9: return "21:9"
|
||||
case .fixed32x9: return "32:9"
|
||||
case .stretched: return "Stretched (Full Screen)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Ryujinx {
|
||||
private var isRunning = false
|
||||
@ -60,6 +40,8 @@ class Ryujinx {
|
||||
@Published var emulationUIView = UIView()
|
||||
@Published var games: [Game] = []
|
||||
|
||||
@Published var defMLContentSize: CGFloat?
|
||||
|
||||
var shouldMetal: Bool {
|
||||
metalLayer == nil
|
||||
}
|
||||
@ -93,6 +75,8 @@ class Ryujinx {
|
||||
var dfsIntegrityChecks: Bool
|
||||
var disablePTC: Bool
|
||||
var disablevsync: Bool
|
||||
var language: SystemLanguage
|
||||
var regioncode: SystemRegionCode
|
||||
|
||||
|
||||
init(gamepath: String,
|
||||
@ -116,7 +100,9 @@ class Ryujinx {
|
||||
expandRam: Bool = false,
|
||||
dfsIntegrityChecks: Bool = false,
|
||||
disablePTC: Bool = false,
|
||||
disablevsync: Bool = false
|
||||
disablevsync: Bool = false,
|
||||
language: SystemLanguage = .americanEnglish,
|
||||
regioncode: SystemRegionCode = .usa
|
||||
) {
|
||||
self.gamepath = gamepath
|
||||
self.inputids = inputids
|
||||
@ -140,6 +126,8 @@ class Ryujinx {
|
||||
self.dfsIntegrityChecks = dfsIntegrityChecks
|
||||
self.disablePTC = disablePTC
|
||||
self.disablevsync = disablevsync
|
||||
self.language = language
|
||||
self.regioncode = regioncode
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,6 +249,10 @@ class Ryujinx {
|
||||
// We don't need this. Ryujinx should handle it fine :3
|
||||
// this also causes crashes in some games :3
|
||||
|
||||
args.append(contentsOf: ["--system-language", config.language.rawValue])
|
||||
|
||||
args.append(contentsOf: ["--system-region", config.regioncode.rawValue])
|
||||
|
||||
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
|
||||
|
||||
if config.nintendoinput {
|
||||
@ -275,7 +267,7 @@ class Ryujinx {
|
||||
args.append("--disable-vsync")
|
||||
}
|
||||
|
||||
|
||||
|
||||
if config.hypervisor {
|
||||
args.append("--use-hypervisor")
|
||||
}
|
||||
@ -294,7 +286,8 @@ class Ryujinx {
|
||||
}
|
||||
|
||||
if config.ignoreMissingServices {
|
||||
args.append(contentsOf: ["--ignore-missing-services", String(config.maxAnisotropy)])
|
||||
// args.append(contentsOf: ["--ignore-missing-services"])
|
||||
args.append("--ignore-missing-services")
|
||||
}
|
||||
|
||||
if config.maxAnisotropy != 0 {
|
||||
@ -317,15 +310,15 @@ class Ryujinx {
|
||||
}
|
||||
|
||||
if config.debuglogs {
|
||||
args.append(contentsOf: ["--enable-debug-logs"])
|
||||
args.append("--enable-debug-logs")
|
||||
}
|
||||
if config.tracelogs {
|
||||
args.append(contentsOf: ["--enable-trace-logs"])
|
||||
args.append("--enable-trace-logs")
|
||||
}
|
||||
|
||||
// List the input ids
|
||||
if config.listinputids {
|
||||
args.append(contentsOf: ["--list-inputs-ids"])
|
||||
args.append("--list-inputs-ids")
|
||||
}
|
||||
|
||||
// Append the input ids (limit to 4 just in case)
|
||||
@ -373,6 +366,18 @@ class Ryujinx {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setTitleUpdate(titleId: String, updatePath: String) {
|
||||
guard let titleIdPtr = titleId.cString(using: .utf8),
|
||||
let updatePathPtr = updatePath.cString(using: .utf8)
|
||||
else {
|
||||
print("Invalid firmware path")
|
||||
return
|
||||
}
|
||||
|
||||
set_title_update(titleIdPtr, updatePathPtr)
|
||||
}
|
||||
|
||||
private func generateGamepadId(joystickIndex: Int32) -> String? {
|
||||
let guid = SDL_JoystickGetDeviceGUID(joystickIndex)
|
||||
|
||||
|
@ -42,7 +42,7 @@ struct ContentView: View {
|
||||
@AppStorage("quit") var quit: Bool = false
|
||||
@State var quits: Bool = false
|
||||
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
||||
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
|
||||
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = true
|
||||
|
||||
// Loading Animation
|
||||
@State private var clumpOffset: CGFloat = -100
|
||||
|
@ -129,6 +129,8 @@ struct ControllerView: View {
|
||||
struct ShoulderButtonsViewLeft: View {
|
||||
@State var width: CGFloat = 160
|
||||
@State var height: CGFloat = 20
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ButtonView(button: .leftTrigger)
|
||||
@ -142,6 +144,9 @@ struct ShoulderButtonsViewLeft: View {
|
||||
width *= 1.2
|
||||
height *= 1.2
|
||||
}
|
||||
|
||||
width *= CGFloat(controllerScale)
|
||||
height *= CGFloat(controllerScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,6 +154,8 @@ struct ShoulderButtonsViewLeft: View {
|
||||
struct ShoulderButtonsViewRight: View {
|
||||
@State var width: CGFloat = 160
|
||||
@State var height: CGFloat = 20
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
ButtonView(button: .rightShoulder)
|
||||
@ -162,12 +169,16 @@ struct ShoulderButtonsViewRight: View {
|
||||
width *= 1.2
|
||||
height *= 1.2
|
||||
}
|
||||
|
||||
width *= CGFloat(controllerScale)
|
||||
height *= CGFloat(controllerScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DPadView: View {
|
||||
@State var size: CGFloat = 145
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
var body: some View {
|
||||
VStack {
|
||||
ButtonView(button: .dPadUp)
|
||||
@ -184,12 +195,16 @@ struct DPadView: View {
|
||||
if UIDevice.current.systemName.contains("iPadOS") {
|
||||
size *= 1.2
|
||||
}
|
||||
|
||||
size *= CGFloat(controllerScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ABXYView: View {
|
||||
@State var size: CGFloat = 145
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ButtonView(button: .X)
|
||||
@ -206,6 +221,8 @@ struct ABXYView: View {
|
||||
if UIDevice.current.systemName.contains("iPadOS") {
|
||||
size *= 1.2
|
||||
}
|
||||
|
||||
size *= CGFloat(controllerScale)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,6 +235,7 @@ struct ButtonView: View {
|
||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
|
||||
|
||||
|
||||
@ -256,6 +274,9 @@ struct ButtonView: View {
|
||||
width *= 1.2
|
||||
height *= 1.2
|
||||
}
|
||||
|
||||
width *= CGFloat(controllerScale)
|
||||
height *= CGFloat(controllerScale)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,14 @@ public struct Joystick: View {
|
||||
@State var iscool: Bool? = nil
|
||||
|
||||
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
var dragDiameter: CGFloat {
|
||||
var selfs = CGFloat(160)
|
||||
selfs *= controllerScale
|
||||
if UIDevice.current.systemName.contains("iPadOS") {
|
||||
return selfs * 1.2
|
||||
}
|
||||
|
||||
return selfs
|
||||
}
|
||||
private let shape: JoystickShape = .circle
|
||||
|
@ -11,16 +11,18 @@ import SwiftUI
|
||||
struct EmulationView: View {
|
||||
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
||||
@State var isPresentedThree: Bool = false
|
||||
@State var isAirplaying = Air.shared.connected
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if isAirplaying {
|
||||
Text("")
|
||||
.onAppear {
|
||||
Air.play(AnyView(MetalView(airplay: true).ignoresSafeArea()))
|
||||
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
||||
}
|
||||
} else {
|
||||
MetalView(airplay: false) // The Emulation View
|
||||
MetalView() // The Emulation View
|
||||
.ignoresSafeArea()
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
@ -31,6 +33,7 @@ struct EmulationView: View {
|
||||
ControllerView() // Virtual Controller
|
||||
}
|
||||
|
||||
|
||||
if ssb {
|
||||
Group {
|
||||
VStack {
|
||||
|
@ -10,7 +10,7 @@ import MetalKit
|
||||
|
||||
struct MetalView: UIViewRepresentable {
|
||||
|
||||
var airplay: Bool // just in case :3
|
||||
var airplay: Bool = Air.shared.connected // just in case :3
|
||||
|
||||
func makeUIView(context: Context) -> UIView {
|
||||
let metalLayer = Ryujinx.shared.metalLayer!
|
||||
|
@ -27,6 +27,7 @@ struct GameLibraryView: View {
|
||||
@State var startgame = false
|
||||
@State var isSelectingGameFile = false
|
||||
@State var isViewingGameInfo: Bool = false
|
||||
@State var isSelectingGameUpdate: Bool = false
|
||||
@State var gameInfo: Game?
|
||||
var games: Binding<[Game]> {
|
||||
Binding(
|
||||
@ -99,7 +100,7 @@ struct GameLibraryView: View {
|
||||
|
||||
LazyVStack(spacing: 2) {
|
||||
ForEach(filteredGames) { game in
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
|
||||
.onTapGesture {
|
||||
addToRecentGames(game)
|
||||
}
|
||||
@ -109,7 +110,7 @@ struct GameLibraryView: View {
|
||||
} else {
|
||||
LazyVStack(spacing: 2) {
|
||||
ForEach(filteredGames) { game in
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, gameInfo: $gameInfo)
|
||||
GameListRow(game: game, startemu: $startemu, games: games, isViewingGameInfo: $isViewingGameInfo, isSelectingGameUpdate: $isSelectingGameUpdate, gameInfo: $gameInfo)
|
||||
.onTapGesture {
|
||||
addToRecentGames(game)
|
||||
}
|
||||
@ -219,7 +220,7 @@ struct GameLibraryView: View {
|
||||
.onChange(of: searchText) { _ in
|
||||
isSearching = !searchText.isEmpty
|
||||
}
|
||||
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .folder]) { result in
|
||||
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .folder, .nsp, .xci]) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
guard url.startAccessingSecurityScopedResource() else {
|
||||
@ -277,6 +278,9 @@ struct GameLibraryView: View {
|
||||
print("File import failed: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isSelectingGameUpdate) {
|
||||
UpdateManagerSheet(game: $gameInfo)
|
||||
}
|
||||
.sheet(isPresented: Binding(
|
||||
get: { isViewingGameInfo && gameInfo != nil },
|
||||
set: { newValue in
|
||||
@ -421,6 +425,7 @@ struct GameListRow: View {
|
||||
@Binding var startemu: Game?
|
||||
@Binding var games: [Game] // Add this binding
|
||||
@Binding var isViewingGameInfo: Bool
|
||||
@Binding var isSelectingGameUpdate: Bool
|
||||
@Binding var gameInfo: Game?
|
||||
@State var gametoDelete: Game?
|
||||
@State var showGameDeleteConfirmation: Bool = false
|
||||
@ -486,6 +491,13 @@ struct GameListRow: View {
|
||||
} label: {
|
||||
Label("Game Info", systemImage: "info.circle")
|
||||
}
|
||||
|
||||
Button {
|
||||
gameInfo = game
|
||||
isSelectingGameUpdate.toggle()
|
||||
} label: {
|
||||
Label("Game Update Manager", systemImage: "chevron.up.circle")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
|
@ -40,9 +40,11 @@ struct SettingsView: View {
|
||||
|
||||
@AppStorage("oldWindowCode") var windowCode: Bool = false
|
||||
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
|
||||
@State private var showResolutionInfo = false
|
||||
@State private var showAnisotropicInfo = false
|
||||
@State private var showControllerInfo = false
|
||||
@State private var searchText = ""
|
||||
|
||||
var filteredMemoryModes: [(String, String)] {
|
||||
@ -270,6 +272,35 @@ struct SettingsView: View {
|
||||
Text("Select input devices and on-screen controls to play with. ")
|
||||
}
|
||||
|
||||
// Language and Region Settings
|
||||
Section {
|
||||
Picker(selection: $config.language) {
|
||||
ForEach(SystemLanguage.allCases, id: \.self) { ratio in
|
||||
Text(ratio.displayName).tag(ratio)
|
||||
}
|
||||
} label: {
|
||||
labelWithIcon("Language", iconName: "character.bubble")
|
||||
}
|
||||
|
||||
Picker(selection: $config.regioncode) {
|
||||
ForEach(SystemRegionCode.allCases, id: \.self) { ratio in
|
||||
Text(ratio.displayName).tag(ratio)
|
||||
}
|
||||
} label: {
|
||||
labelWithIcon("Region", iconName: "globe")
|
||||
}
|
||||
|
||||
|
||||
// globe
|
||||
} header: {
|
||||
Text("Language and Region Settings")
|
||||
.font(.title3.weight(.semibold))
|
||||
.textCase(nil)
|
||||
.headerProminence(.increased)
|
||||
} footer: {
|
||||
Text("Configure the System Language and the Region.")
|
||||
}
|
||||
|
||||
// Input Settings
|
||||
Section {
|
||||
|
||||
@ -283,6 +314,46 @@ struct SettingsView: View {
|
||||
}
|
||||
.tint(.blue)
|
||||
.disabled(true)
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
labelWithIcon("On-Screen Controller Scale", iconName: "magnifyingglass")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Button {
|
||||
showControllerInfo.toggle()
|
||||
} label: {
|
||||
Image(systemName: "info.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help("Learn more about On-Screen Controller Scale")
|
||||
.alert(isPresented: $showControllerInfo) {
|
||||
Alert(
|
||||
title: Text("On-Screen Controller Scale"),
|
||||
message: Text("Adjust the On-Screen Controller size."),
|
||||
dismissButton: .default(Text("OK"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Slider(value: $controllerScale, in: 0.1...3.0, step: 0.05) {
|
||||
Text("Resolution Scale")
|
||||
} minimumValueLabel: {
|
||||
Text("0.1x")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
} maximumValueLabel: {
|
||||
Text("3.0x")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Text("\(controllerScale, specifier: "%.2f")x")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
} header: {
|
||||
Text("Input Settings")
|
||||
.font(.title3.weight(.semibold))
|
||||
|
191
src/MeloNX/MeloNX/App/Views/Updates/GameUpdateManagerSheet.swift
Normal file
191
src/MeloNX/MeloNX/App/Views/Updates/GameUpdateManagerSheet.swift
Normal file
@ -0,0 +1,191 @@
|
||||
//
|
||||
// GameUpdateManagerSheet.swift
|
||||
// MeloNX
|
||||
//
|
||||
// Created by Stossy11 on 16/02/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct UpdateManagerSheet: View {
|
||||
@State private var items: [String] = []
|
||||
@State private var paths: [URL] = []
|
||||
@State private var selectedItem: String? = nil
|
||||
@Binding var game: Game?
|
||||
@State private var isSelectingGameUpdate = false
|
||||
@State private var jsonURL: URL? = nil
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
List(paths, id: \..self) { item in
|
||||
Button(action: {
|
||||
selectItem(item.lastPathComponent)
|
||||
}) {
|
||||
HStack {
|
||||
Text(item.lastPathComponent)
|
||||
if selectedItem == "\(game!.titleId)/\(item.lastPathComponent)" {
|
||||
Spacer()
|
||||
Image(systemName: "checkmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
removeUpdate(item)
|
||||
} label: {
|
||||
Text("Remove Update")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
print(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json"))
|
||||
|
||||
loadJSON(URL.documentsDirectory.appendingPathComponent("games").appendingPathComponent(game!.titleId).appendingPathComponent("updates.json"))
|
||||
}
|
||||
.navigationTitle("\(game!.titleName) Updates")
|
||||
.toolbar {
|
||||
Button("+") {
|
||||
isSelectingGameUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.fileImporter(isPresented: $isSelectingGameUpdate, allowedContentTypes: [.item]) { result in
|
||||
switch result {
|
||||
case .success(let url):
|
||||
guard url.startAccessingSecurityScopedResource() else {
|
||||
print("Failed to access security-scoped resource")
|
||||
return
|
||||
}
|
||||
defer { url.stopAccessingSecurityScopedResource() }
|
||||
|
||||
let gameInfo = game!
|
||||
|
||||
do {
|
||||
let fileManager = FileManager.default
|
||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let updatedDirectory = documentsDirectory.appendingPathComponent("updates")
|
||||
let romUpdatedDirectory = updatedDirectory.appendingPathComponent(gameInfo.titleId)
|
||||
|
||||
if !fileManager.fileExists(atPath: updatedDirectory.path) {
|
||||
try fileManager.createDirectory(at: updatedDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
if !fileManager.fileExists(atPath: romUpdatedDirectory.path) {
|
||||
try fileManager.createDirectory(at: romUpdatedDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
let destinationURL = romUpdatedDirectory.appendingPathComponent(url.lastPathComponent)
|
||||
try? fileManager.copyItem(at: url, to: destinationURL)
|
||||
|
||||
Ryujinx.shared.setTitleUpdate(titleId: gameInfo.titleId, updatePath: "\(gameInfo.titleId)/" + url.lastPathComponent)
|
||||
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||
loadJSON(jsonURL!)
|
||||
} catch {
|
||||
print("Error copying game file: \(error)")
|
||||
}
|
||||
case .failure(let err):
|
||||
print("File import failed: \(err.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeUpdate(_ game: URL) {
|
||||
let gameString = "\(self.game!.titleId)/\(game.lastPathComponent)"
|
||||
paths.removeAll { $0 == game }
|
||||
items.removeAll { $0 == gameString }
|
||||
|
||||
if selectedItem == gameString {
|
||||
selectedItem = nil
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.removeItem(at: game)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
saveJSON(selectedItem: selectedItem ?? "")
|
||||
}
|
||||
|
||||
func saveJSON(selectedItem: String) {
|
||||
guard let jsonURL = jsonURL else { return }
|
||||
do {
|
||||
let jsonDict = ["paths": items, "selected": selectedItem] as [String: Any]
|
||||
let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
|
||||
try newData.write(to: jsonURL)
|
||||
} catch {
|
||||
print("Failed to update JSON: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func loadJSON(_ json: URL) {
|
||||
|
||||
self.jsonURL = json
|
||||
print("Failed to read JSO")
|
||||
|
||||
guard let jsonURL = jsonURL else { return }
|
||||
print("Failed to read JSOK")
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: jsonURL)
|
||||
if let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||
let list = jsonDict["paths"] as? [String] {
|
||||
var urls: [URL] = []
|
||||
|
||||
for path in list {
|
||||
urls.append(URL.documentsDirectory.appendingPathComponent("updates").appendingPathComponent(path))
|
||||
}
|
||||
|
||||
self.items = list
|
||||
self.paths = urls
|
||||
self.selectedItem = jsonDict["selected"] as? String
|
||||
}
|
||||
} catch {
|
||||
print("Failed to read JSON: \(error)")
|
||||
createDefaultJSON()
|
||||
}
|
||||
}
|
||||
|
||||
func createDefaultJSON() {
|
||||
guard let jsonURL = jsonURL else { return }
|
||||
let defaultData: [String: Any] = ["selected": "", "paths": []]
|
||||
do {
|
||||
let newData = try JSONSerialization.data(withJSONObject: defaultData, options: .prettyPrinted)
|
||||
try newData.write(to: jsonURL)
|
||||
self.items = []
|
||||
self.selectedItem = ""
|
||||
} catch {
|
||||
print("Failed to create default JSON: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func selectItem(_ item: String) {
|
||||
let newSelection = "\(game!.titleId)/\(item)"
|
||||
|
||||
guard let jsonURL = jsonURL else { return }
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: jsonURL)
|
||||
var jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:]
|
||||
|
||||
if let currentSelected = jsonDict["selected"] as? String, currentSelected == newSelection {
|
||||
jsonDict["selected"] = ""
|
||||
selectedItem = ""
|
||||
} else {
|
||||
jsonDict["selected"] = newSelection
|
||||
selectedItem = newSelection
|
||||
}
|
||||
|
||||
jsonDict["paths"] = items
|
||||
|
||||
let newData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
|
||||
try newData.write(to: jsonURL)
|
||||
} catch {
|
||||
print("Failed to update JSON: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
@ -15,6 +15,17 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GCSupportedGameControllers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ProfileName</key>
|
||||
<string>ExtendedGamepad</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ProfileName</key>
|
||||
<string>MicroGamepad</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>melonx</string>
|
||||
@ -25,6 +36,11 @@
|
||||
<array>
|
||||
<string>LaunchGameIntent</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>processing</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
|
@ -115,6 +115,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
private static bool _enableMouse;
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "main_ryujinx_sdl")]
|
||||
public static unsafe int MainExternal(int argCount, IntPtr* pArgs)
|
||||
@ -141,6 +142,34 @@ namespace Ryujinx.Headless.SDL2
|
||||
return 0;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "set_title_update")]
|
||||
public static unsafe void SetTitleUpdate(IntPtr titleIdPtr, IntPtr updatePathPtr) {
|
||||
var titleId = Marshal.PtrToStringAnsi(titleIdPtr);
|
||||
var updatePath = Marshal.PtrToStringAnsi(updatePathPtr);
|
||||
string _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
|
||||
|
||||
TitleUpdateMetadata _titleUpdateWindowData;
|
||||
|
||||
if (File.Exists(_updateJsonPath)) {
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath, _titleSerializerContext.TitleUpdateMetadata);
|
||||
|
||||
_titleUpdateWindowData.Paths ??= new List<string>();
|
||||
if (!_titleUpdateWindowData.Paths.Contains(updatePath)) {
|
||||
_titleUpdateWindowData.Paths.Add(updatePath);
|
||||
}
|
||||
|
||||
_titleUpdateWindowData.Selected = updatePath;
|
||||
} else {
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata {
|
||||
Selected = updatePath,
|
||||
Paths = new List<string> { updatePath },
|
||||
};
|
||||
}
|
||||
|
||||
JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, _titleSerializerContext.TitleUpdateMetadata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "get_current_fps")]
|
||||
public static unsafe int GetFPS()
|
||||
@ -304,7 +333,6 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
if (_window != null)
|
||||
{
|
||||
|
||||
_window.Exit();
|
||||
_emulationContext.Dispose();
|
||||
_emulationContext = null;
|
||||
@ -317,10 +345,14 @@ namespace Ryujinx.Headless.SDL2
|
||||
if (_virtualFileSystem == null) {
|
||||
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
||||
}
|
||||
|
||||
var extension = Marshal.PtrToStringAnsi(extensionPtr);
|
||||
var stream = OpenFile(descriptor);
|
||||
|
||||
var gameInfo = GetGameInfo(stream, extension);
|
||||
if (gameInfo == null) {
|
||||
return new GameInfoNative(0, "", "", "", "", new byte[0]);
|
||||
}
|
||||
|
||||
return new GameInfoNative(
|
||||
(ulong)gameInfo.FileSize,
|
||||
@ -719,7 +751,7 @@ namespace Ryujinx.Headless.SDL2
|
||||
|
||||
if (File.Exists(titleUpdateMetadataPath))
|
||||
{
|
||||
// updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected;
|
||||
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
||||
|
||||
if (File.Exists(updatePath))
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user