some changes™
This commit is contained in:
parent
90859393a3
commit
bff023563b
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "fedf09a893a63378a2e53f631cd833ae83a0c9ee7338eb8d153b04fd34aaf805",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "swiftsvg",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/mchoe/SwiftSVG",
|
||||||
|
"state" : {
|
||||||
|
"branch" : "master",
|
||||||
|
"revision" : "88b9ee086b29019e35f6f49c8e30e5552eb8fa9d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
Binary file not shown.
@ -50,7 +50,9 @@ char* installed_firmware_version();
|
|||||||
|
|
||||||
void set_native_window(void *layerPtr);
|
void set_native_window(void *layerPtr);
|
||||||
|
|
||||||
void stop_emulation(bool shouldPause);
|
void pause_emulation(bool shouldPause);
|
||||||
|
|
||||||
|
void stop_emulation();
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ struct iOSNav<Content: View>: View {
|
|||||||
|
|
||||||
|
|
||||||
class Ryujinx : ObservableObject {
|
class Ryujinx : ObservableObject {
|
||||||
private var isRunning = false
|
@Published var isRunning = false
|
||||||
|
|
||||||
let virtualController = VirtualController()
|
let virtualController = VirtualController()
|
||||||
|
|
||||||
@ -147,6 +147,22 @@ class Ryujinx : ObservableObject {
|
|||||||
self.games = loadGames()
|
self.games = loadGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runloop(_ cool: @escaping () -> Void) {
|
||||||
|
if UserDefaults.standard.bool(forKey: "runOnMainThread") {
|
||||||
|
RunLoop.main.perform {
|
||||||
|
cool()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread = Thread {
|
||||||
|
cool()
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.qualityOfService = .userInteractive
|
||||||
|
thread.name = "MeloNX"
|
||||||
|
thread.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct Configuration : Codable, Equatable {
|
public struct Configuration : Codable, Equatable {
|
||||||
var gamepath: String
|
var gamepath: String
|
||||||
var inputids: [String]
|
var inputids: [String]
|
||||||
@ -238,7 +254,7 @@ class Ryujinx : ObservableObject {
|
|||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
|
||||||
thread = Thread { [self] in
|
runloop { [self] in
|
||||||
|
|
||||||
isRunning = true
|
isRunning = true
|
||||||
|
|
||||||
@ -299,10 +315,6 @@ class Ryujinx : ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.qualityOfService = .userInteractive
|
|
||||||
thread.name = "MeloNX"
|
|
||||||
thread.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveArrayAsTextFile(strings: [String], filePath: String) {
|
func saveArrayAsTextFile(strings: [String], filePath: String) {
|
||||||
@ -374,10 +386,6 @@ class Ryujinx : ObservableObject {
|
|||||||
thread.cancel()
|
thread.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
var running: Bool {
|
|
||||||
return isRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func loadGames() -> [Game] {
|
func loadGames() -> [Game] {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
@ -468,6 +476,7 @@ class Ryujinx : ObservableObject {
|
|||||||
|
|
||||||
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
|
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
|
||||||
|
|
||||||
|
|
||||||
if config.nintendoinput {
|
if config.nintendoinput {
|
||||||
args.append("--correct-controller")
|
args.append("--correct-controller")
|
||||||
}
|
}
|
||||||
|
27
src/MeloNX/MeloNX/App/Models/ToggleButtonsState.swift
Normal file
27
src/MeloNX/MeloNX/App/Models/ToggleButtonsState.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// ToggleButtonsState.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 12/04/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
struct ToggleButtonsState: Codable, Equatable {
|
||||||
|
var toggle1: Bool
|
||||||
|
var toggle2: Bool
|
||||||
|
var toggle3: Bool
|
||||||
|
var toggle4: Bool
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self = .default
|
||||||
|
}
|
||||||
|
|
||||||
|
init(toggle1: Bool, toggle2: Bool, toggle3: Bool, toggle4: Bool) {
|
||||||
|
self.toggle1 = toggle1
|
||||||
|
self.toggle2 = toggle2
|
||||||
|
self.toggle3 = toggle3
|
||||||
|
self.toggle4 = toggle4
|
||||||
|
}
|
||||||
|
|
||||||
|
static let `default` = ToggleButtonsState(toggle1: false, toggle2: false, toggle3: false, toggle4: false)
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// AppCodableStorage.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 12/04/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@propertyWrapper
|
||||||
|
struct AppCodableStorage<Value: Codable & Equatable>: DynamicProperty {
|
||||||
|
@State private var value: Value
|
||||||
|
|
||||||
|
private let key: String
|
||||||
|
private let defaultValue: Value
|
||||||
|
private let storage: UserDefaults
|
||||||
|
|
||||||
|
init(wrappedValue defaultValue: Value, _ key: String, store: UserDefaults = .standard) {
|
||||||
|
self._value = State(initialValue: {
|
||||||
|
if let data = store.data(forKey: key),
|
||||||
|
let decoded = try? JSONDecoder().decode(Value.self, from: data) {
|
||||||
|
return decoded
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}())
|
||||||
|
self.key = key
|
||||||
|
self.defaultValue = defaultValue
|
||||||
|
self.storage = store
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrappedValue: Value {
|
||||||
|
get { value }
|
||||||
|
nonmutating set {
|
||||||
|
value = newValue
|
||||||
|
if let data = try? JSONEncoder().encode(newValue) {
|
||||||
|
storage.set(data, forKey: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectedValue: Binding<Value> {
|
||||||
|
Binding(
|
||||||
|
get: { self.wrappedValue },
|
||||||
|
set: { newValue in self.wrappedValue = newValue }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
125
src/MeloNX/MeloNX/App/Views/Main/Elements/FileImporter.swift
Normal file
125
src/MeloNX/MeloNX/App/Views/Main/Elements/FileImporter.swift
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// FileImporter.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 17/04/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
class FileImporterManager: ObservableObject {
|
||||||
|
static let shared = FileImporterManager()
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
func importFiles(types: [UTType], allowMultiple: Bool = false, completion: @escaping (Result<[URL], Error>) -> Void) {
|
||||||
|
let id = "\(Unmanaged.passUnretained(completion as AnyObject).toOpaque())"
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .importFiles,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [
|
||||||
|
"id": id,
|
||||||
|
"types": types,
|
||||||
|
"allowMultiple": allowMultiple,
|
||||||
|
"completion": completion
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Notification.Name {
|
||||||
|
static let importFiles = Notification.Name("importFiles")
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileImporterView: ViewModifier {
|
||||||
|
@State private var isImporterPresented: [String: Bool] = [:]
|
||||||
|
@State private var activeImporters: [String: ImporterConfig] = [:]
|
||||||
|
|
||||||
|
struct ImporterConfig {
|
||||||
|
let types: [UTType]
|
||||||
|
let allowMultiple: Bool
|
||||||
|
let completion: (Result<[URL], Error>) -> Void
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.background(
|
||||||
|
ForEach(Array(activeImporters.keys), id: \.self) { id in
|
||||||
|
if let config = activeImporters[id] {
|
||||||
|
FileImporterWrapper(
|
||||||
|
isPresented: Binding(
|
||||||
|
get: { isImporterPresented[id] ?? false },
|
||||||
|
set: { isImporterPresented[id] = $0 }
|
||||||
|
),
|
||||||
|
id: id,
|
||||||
|
config: config,
|
||||||
|
onCompletion: { success in
|
||||||
|
if success {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
activeImporters.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .importFiles)) { notification in
|
||||||
|
guard let userInfo = notification.userInfo,
|
||||||
|
let id = userInfo["id"] as? String,
|
||||||
|
let types = userInfo["types"] as? [UTType],
|
||||||
|
let allowMultiple = userInfo["allowMultiple"] as? Bool,
|
||||||
|
let completion = userInfo["completion"] as? ((Result<[URL], Error>) -> Void) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = ImporterConfig(
|
||||||
|
types: types,
|
||||||
|
allowMultiple: allowMultiple,
|
||||||
|
completion: completion
|
||||||
|
)
|
||||||
|
|
||||||
|
activeImporters[id] = config
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
isImporterPresented[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FileImporterWrapper: View {
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
let id: String
|
||||||
|
let config: FileImporterView.ImporterConfig
|
||||||
|
let onCompletion: (Bool) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Text("wow")
|
||||||
|
.hidden()
|
||||||
|
.fileImporter(
|
||||||
|
isPresented: $isPresented,
|
||||||
|
allowedContentTypes: config.types,
|
||||||
|
allowsMultipleSelection: config.allowMultiple
|
||||||
|
) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let urls):
|
||||||
|
config.completion(.success(urls))
|
||||||
|
case .failure(let error):
|
||||||
|
config.completion(.failure(error))
|
||||||
|
}
|
||||||
|
onCompletion(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func withFileImporter() -> some View {
|
||||||
|
self.modifier(FileImporterView())
|
||||||
|
}
|
||||||
|
}
|
@ -70,11 +70,11 @@ struct ControllerView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
ButtonView(button: .leftStick)
|
ButtonView(button: .leftStick)
|
||||||
.padding()
|
.padding()
|
||||||
ButtonView(button: .start)
|
ButtonView(button: .back)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
ButtonView(button: .back)
|
ButtonView(button: .start)
|
||||||
ButtonView(button: .rightStick)
|
ButtonView(button: .rightStick)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
@ -257,148 +257,180 @@ struct ABXYView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ButtonView: View {
|
struct ButtonView: View {
|
||||||
var button: VirtualControllerButton
|
var button: VirtualControllerButton
|
||||||
@State private var width: CGFloat = 45
|
|
||||||
@State private var height: CGFloat = 45
|
|
||||||
@State private var isPressed = false
|
|
||||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||||
@Environment(\.presentationMode) var presentationMode
|
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
@State private var debounceTimer: Timer?
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
@AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState()
|
||||||
|
@State private var istoggle = false
|
||||||
|
|
||||||
|
@State private var isPressed = false
|
||||||
|
@State private var toggleState = false
|
||||||
|
|
||||||
|
@State private var size: CGSize = .zero
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Image(systemName: buttonText)
|
Circle()
|
||||||
|
.foregroundStyle(.clear.opacity(0))
|
||||||
|
.overlay {
|
||||||
|
Image(systemName: buttonConfig.iconName)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width, height: height)
|
.frame(width: size.width, height: size.height)
|
||||||
.foregroundColor(true ? Color.white.opacity(0.5) : Color.black.opacity(0.5))
|
.foregroundStyle(.white)
|
||||||
|
.opacity(isPressed ? 0.6 : 1.0)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
|
.frame(width: size.width, height: size.height)
|
||||||
.background(
|
.background(
|
||||||
|
buttonBackground
|
||||||
|
)
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { _ in handleButtonPress() }
|
||||||
|
.onEnded { _ in handleButtonRelease() }
|
||||||
|
)
|
||||||
|
.onAppear {
|
||||||
|
istoggle = (toggleButtons.toggle1 && button == .A) || (toggleButtons.toggle2 && button == .B) || (toggleButtons.toggle3 && button == .X) || (toggleButtons.toggle4 && button == .Y)
|
||||||
|
size = calculateButtonSize()
|
||||||
|
}
|
||||||
|
.onChange(of: controllerScale) { _ in
|
||||||
|
size = calculateButtonSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var buttonBackground: some View {
|
||||||
Group {
|
Group {
|
||||||
if !button.isTrigger && button != .leftStick && button != .rightStick {
|
if !button.isTrigger && button != .leftStick && button != .rightStick {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.fill(Color.gray.opacity(0.4))
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: size.width * 1.25, height: size.height * 1.25)
|
||||||
} else if button == .leftStick || button == .rightStick {
|
} else if button == .leftStick || button == .rightStick {
|
||||||
Image(systemName: buttonText)
|
Image(systemName: buttonConfig.iconName)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: size.width * 1.25, height: size.height * 1.25)
|
||||||
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.foregroundColor(Color.gray.opacity(0.4))
|
||||||
} else if button.isTrigger {
|
} else if button.isTrigger {
|
||||||
Image(systemName: "" + String(turntobutton(buttonText)))
|
Image(systemName: convertTriggerIconToButton(buttonConfig.iconName))
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: width * 1.25, height: height * 1.25)
|
.frame(width: size.width * 1.25, height: size.height * 1.25)
|
||||||
.foregroundColor(true ? Color.gray.opacity(0.4) : Color.gray.opacity(0.3))
|
.foregroundColor(Color.gray.opacity(0.4))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.opacity(isPressed ? 0.6 : 1.0)
|
|
||||||
.gesture(
|
|
||||||
DragGesture(minimumDistance: 0)
|
|
||||||
.onChanged { _ in
|
|
||||||
handleButtonPress()
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
handleButtonRelease()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.onAppear {
|
|
||||||
configureSizeForButton()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func turntobutton(_ string: String) -> String {
|
private func convertTriggerIconToButton(_ iconName: String) -> String {
|
||||||
var sting = string
|
if iconName.hasPrefix("zl") || iconName.hasPrefix("zr") {
|
||||||
if string.hasPrefix("zl") || string.hasPrefix("zr") {
|
var converted = String(iconName.dropFirst(3))
|
||||||
sting = String(string.dropFirst(3))
|
converted = converted.replacingOccurrences(of: "rectangle", with: "button")
|
||||||
|
converted = converted.replacingOccurrences(of: ".fill", with: ".horizontal.fill")
|
||||||
|
return converted
|
||||||
} else {
|
} else {
|
||||||
sting = String(string.dropFirst(2))
|
var converted = String(iconName.dropFirst(2))
|
||||||
|
converted = converted.replacingOccurrences(of: "rectangle", with: "button")
|
||||||
|
converted = converted.replacingOccurrences(of: ".fill", with: ".horizontal.fill")
|
||||||
|
return converted
|
||||||
}
|
}
|
||||||
sting = sting.replacingOccurrences(of: "rectangle", with: "button")
|
|
||||||
sting = sting.replacingOccurrences(of: ".fill", with: ".horizontal.fill")
|
|
||||||
|
|
||||||
return sting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleButtonPress() {
|
private func handleButtonPress() {
|
||||||
if !isPressed {
|
guard !isPressed || istoggle else { return }
|
||||||
|
|
||||||
|
if istoggle {
|
||||||
|
toggleState.toggle()
|
||||||
|
isPressed = toggleState
|
||||||
|
let value = toggleState ? 1 : 0
|
||||||
|
Ryujinx.shared.virtualController.setButtonState(Uint8(value), for: button)
|
||||||
|
Haptics.shared.play(.medium)
|
||||||
|
} else {
|
||||||
isPressed = true
|
isPressed = true
|
||||||
|
|
||||||
debounceTimer?.invalidate()
|
|
||||||
|
|
||||||
Ryujinx.shared.virtualController.setButtonState(1, for: button)
|
Ryujinx.shared.virtualController.setButtonState(1, for: button)
|
||||||
|
|
||||||
Haptics.shared.play(.medium)
|
Haptics.shared.play(.medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleButtonRelease() {
|
private func handleButtonRelease() {
|
||||||
if isPressed {
|
if istoggle { return }
|
||||||
isPressed = false
|
|
||||||
|
|
||||||
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: false) { _ in
|
guard isPressed else { return }
|
||||||
|
|
||||||
|
isPressed = false
|
||||||
|
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.05) {
|
||||||
Ryujinx.shared.virtualController.setButtonState(0, for: button)
|
Ryujinx.shared.virtualController.setButtonState(0, for: button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private func configureSizeForButton() {
|
private func calculateButtonSize() -> CGSize {
|
||||||
|
let baseWidth: CGFloat
|
||||||
|
let baseHeight: CGFloat
|
||||||
|
|
||||||
if button.isTrigger {
|
if button.isTrigger {
|
||||||
width = 70
|
baseWidth = 70
|
||||||
height = 40
|
baseHeight = 40
|
||||||
} else if button.isSmall {
|
} else if button.isSmall {
|
||||||
width = 35
|
baseWidth = 35
|
||||||
height = 35
|
baseHeight = 35
|
||||||
|
} else {
|
||||||
|
baseWidth = 45
|
||||||
|
baseHeight = 45
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust for iPad
|
let deviceMultiplier = UIDevice.current.userInterfaceIdiom == .pad ? 1.2 : 1.0
|
||||||
if UIDevice.current.systemName.contains("iPadOS") {
|
let scaleMultiplier = CGFloat(controllerScale)
|
||||||
width *= 1.2
|
|
||||||
height *= 1.2
|
return CGSize(
|
||||||
|
width: baseWidth * deviceMultiplier * scaleMultiplier,
|
||||||
|
height: baseHeight * deviceMultiplier * scaleMultiplier
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
width *= CGFloat(controllerScale)
|
// Centralized button configuration
|
||||||
height *= CGFloat(controllerScale)
|
private var buttonConfig: ButtonConfiguration {
|
||||||
}
|
|
||||||
|
|
||||||
private var buttonText: String {
|
|
||||||
switch button {
|
switch button {
|
||||||
case .A:
|
case .A:
|
||||||
return "a.circle.fill"
|
return ButtonConfiguration(iconName: "a.circle.fill")
|
||||||
case .B:
|
case .B:
|
||||||
return "b.circle.fill"
|
return ButtonConfiguration(iconName: "b.circle.fill")
|
||||||
case .X:
|
case .X:
|
||||||
return "x.circle.fill"
|
return ButtonConfiguration(iconName: "x.circle.fill")
|
||||||
case .Y:
|
case .Y:
|
||||||
return "y.circle.fill"
|
return ButtonConfiguration(iconName: "y.circle.fill")
|
||||||
case .leftStick:
|
case .leftStick:
|
||||||
return "l.joystick.press.down.fill"
|
return ButtonConfiguration(iconName: "l.joystick.press.down.fill")
|
||||||
case .rightStick:
|
case .rightStick:
|
||||||
return "r.joystick.press.down.fill"
|
return ButtonConfiguration(iconName: "r.joystick.press.down.fill")
|
||||||
case .dPadUp:
|
case .dPadUp:
|
||||||
return "arrowtriangle.up.circle.fill"
|
return ButtonConfiguration(iconName: "arrowtriangle.up.circle.fill")
|
||||||
case .dPadDown:
|
case .dPadDown:
|
||||||
return "arrowtriangle.down.circle.fill"
|
return ButtonConfiguration(iconName: "arrowtriangle.down.circle.fill")
|
||||||
case .dPadLeft:
|
case .dPadLeft:
|
||||||
return "arrowtriangle.left.circle.fill"
|
return ButtonConfiguration(iconName: "arrowtriangle.left.circle.fill")
|
||||||
case .dPadRight:
|
case .dPadRight:
|
||||||
return "arrowtriangle.right.circle.fill"
|
return ButtonConfiguration(iconName: "arrowtriangle.right.circle.fill")
|
||||||
case .leftTrigger:
|
case .leftTrigger:
|
||||||
return "zl.rectangle.roundedtop.fill"
|
return ButtonConfiguration(iconName: "zl.rectangle.roundedtop.fill")
|
||||||
case .rightTrigger:
|
case .rightTrigger:
|
||||||
return "zr.rectangle.roundedtop.fill"
|
return ButtonConfiguration(iconName: "zr.rectangle.roundedtop.fill")
|
||||||
case .leftShoulder:
|
case .leftShoulder:
|
||||||
return "l.rectangle.roundedbottom.fill"
|
return ButtonConfiguration(iconName: "l.rectangle.roundedbottom.fill")
|
||||||
case .rightShoulder:
|
case .rightShoulder:
|
||||||
return "r.rectangle.roundedbottom.fill"
|
return ButtonConfiguration(iconName: "r.rectangle.roundedbottom.fill")
|
||||||
case .start:
|
case .start:
|
||||||
return "plus.circle.fill"
|
return ButtonConfiguration(iconName: "plus.circle.fill")
|
||||||
case .back:
|
case .back:
|
||||||
return "minus.circle.fill"
|
return ButtonConfiguration(iconName: "minus.circle.fill")
|
||||||
case .guide:
|
case .guide:
|
||||||
return "house.circle.fill"
|
return ButtonConfiguration(iconName: "house.circle.fill")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ButtonConfiguration {
|
||||||
|
let iconName: String
|
||||||
|
}
|
||||||
}
|
}
|
@ -65,28 +65,27 @@ struct EmulationView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if ssb {
|
if ssb {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Button {
|
Image(systemName: "arrow.left.circle")
|
||||||
if let screenshot = Ryujinx.shared.emulationUIView?.screenshot() {
|
.resizable()
|
||||||
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
|
.frame(width: 50, height: 50)
|
||||||
|
.onTapGesture {
|
||||||
|
startgame = nil
|
||||||
|
stop_emulation()
|
||||||
|
try? Ryujinx.shared.stop()
|
||||||
}
|
}
|
||||||
} label: {
|
|
||||||
Image(systemName: "square.and.arrow.up")
|
|
||||||
}
|
|
||||||
.frame(width: UIDevice.current.systemName.contains("iPadOS") ? 60 * 1.2 : 45, height: UIDevice.current.systemName.contains("iPadOS") ? 60 * 1.2 : 45)
|
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,13 +101,13 @@ struct EmulationView: View {
|
|||||||
.onChange(of: scenePhase) { newPhase in
|
.onChange(of: scenePhase) { newPhase in
|
||||||
// Detect when the app enters the background
|
// Detect when the app enters the background
|
||||||
if newPhase == .background {
|
if newPhase == .background {
|
||||||
stop_emulation(true)
|
pause_emulation(true)
|
||||||
isInBackground = true
|
isInBackground = true
|
||||||
} else if newPhase == .active {
|
} else if newPhase == .active {
|
||||||
stop_emulation(false)
|
pause_emulation(false)
|
||||||
isInBackground = false
|
isInBackground = false
|
||||||
} else if newPhase == .inactive {
|
} else if newPhase == .inactive {
|
||||||
stop_emulation(true)
|
pause_emulation(true)
|
||||||
isInBackground = true
|
isInBackground = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,11 @@ class MeloMTKView: MTKView {
|
|||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesBegan(touches, with: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
|
let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
|
||||||
|
|
||||||
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
||||||
|
|
||||||
|
if !disabled {
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
let location = touch.location(in: self)
|
let location = touch.location(in: self)
|
||||||
if scaleToTargetResolution(location) == nil {
|
if scaleToTargetResolution(location) == nil {
|
||||||
@ -104,12 +107,16 @@ class MeloMTKView: MTKView {
|
|||||||
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesEnded(touches, with: event)
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
|
let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
|
||||||
|
|
||||||
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
||||||
|
|
||||||
|
if !disabled {
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
if ignoredTouches.contains(touch) {
|
if ignoredTouches.contains(touch) {
|
||||||
ignoredTouches.remove(touch)
|
ignoredTouches.remove(touch)
|
||||||
@ -124,12 +131,16 @@ class MeloMTKView: MTKView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesMoved(touches, with: event)
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
|
let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
|
||||||
|
|
||||||
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
||||||
|
|
||||||
|
if !disabled {
|
||||||
for touch in touches {
|
for touch in touches {
|
||||||
if ignoredTouches.contains(touch) {
|
if ignoredTouches.contains(touch) {
|
||||||
continue
|
continue
|
||||||
@ -151,4 +162,5 @@ class MeloMTKView: MTKView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ struct ContentView: View {
|
|||||||
@AppStorage("quit") var quit: Bool = false
|
@AppStorage("quit") var quit: Bool = false
|
||||||
@State var quits: Bool = false
|
@State var quits: Bool = false
|
||||||
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
|
||||||
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = true
|
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = false
|
||||||
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
|
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
|
||||||
|
|
||||||
// Loading Animation
|
// Loading Animation
|
@ -78,10 +78,7 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
// Game list
|
// Game list
|
||||||
if Ryujinx.shared.games.isEmpty {
|
if Ryujinx.shared.games.isEmpty {
|
||||||
EmptyGameLibraryView(
|
EmptyGameLibraryView(isSelectingGameFile: $isSelectingGameFile)
|
||||||
isSelectingGameFile: $isSelectingGameFile,
|
|
||||||
isImporting: $isImporting
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
gameListView
|
gameListView
|
||||||
.animation(.easeInOut(duration: 0.3), value: searchText)
|
.animation(.easeInOut(duration: 0.3), value: searchText)
|
||||||
@ -174,12 +171,30 @@ struct GameLibraryView: View {
|
|||||||
.onChange(of: searchText) { _ in
|
.onChange(of: searchText) { _ in
|
||||||
isSearching = !searchText.isEmpty
|
isSearching = !searchText.isEmpty
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.folder, .nsp, .xci, .zip, .item]) { result in
|
.onChange(of: isImporting) { newValue in
|
||||||
handleFileImport(result: result)
|
if newValue {
|
||||||
|
FileImporterManager.shared.importFiles(types: [.nsp, .xci, .item]) { result in
|
||||||
|
isImporting = false
|
||||||
|
handleRunningGame(result: result)
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: isSelectingGameFile) { newValue in
|
||||||
|
if newValue {
|
||||||
|
FileImporterManager.shared.importFiles(types: [.nsp, .xci, .item]) { result in
|
||||||
|
isImporting = false
|
||||||
|
handleAddingGame(result: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: firmwareInstaller) { newValue in
|
||||||
|
if newValue {
|
||||||
|
FileImporterManager.shared.importFiles(types: [.folder, .zip]) { result in
|
||||||
|
isImporting = false
|
||||||
handleFirmwareImport(result: result)
|
handleFirmwareImport(result: result)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.sheet(isPresented: $isSelectingGameUpdate) {
|
.sheet(isPresented: $isSelectingGameUpdate) {
|
||||||
UpdateManagerSheet(game: $gameInfo)
|
UpdateManagerSheet(game: $gameInfo)
|
||||||
}
|
}
|
||||||
@ -361,11 +376,10 @@ struct GameLibraryView: View {
|
|||||||
|
|
||||||
// MARK: - Import Handlers
|
// MARK: - Import Handlers
|
||||||
|
|
||||||
private func handleFileImport(result: Result<URL, Error>) {
|
private func handleAddingGame(result: Result<[URL], Error>) {
|
||||||
if isSelectingGameFile {
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let url):
|
case .success(let urls):
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
guard let url = urls.first, url.startAccessingSecurityScopedResource() else {
|
||||||
// print("Failed to access security-scoped resource")
|
// print("Failed to access security-scoped resource")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -390,10 +404,12 @@ struct GameLibraryView: View {
|
|||||||
case .failure(let err):
|
case .failure(let err):
|
||||||
print("File import failed: \(err.localizedDescription)")
|
print("File import failed: \(err.localizedDescription)")
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
private func handleRunningGame(result: Result<[URL], Error>) {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let url):
|
case .success(let urls):
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
guard let url = urls.first, url.startAccessingSecurityScopedResource() else {
|
||||||
// print("Failed to access security-scoped resource")
|
// print("Failed to access security-scoped resource")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -418,11 +434,14 @@ struct GameLibraryView: View {
|
|||||||
print("File import failed: \(err.localizedDescription)")
|
print("File import failed: \(err.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private func handleFirmwareImport(result: Result<URL, Error>) {
|
private func handleFirmwareImport(result: Result<[URL], Error>) {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
|
guard let url = url.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let fun = url.startAccessingSecurityScopedResource()
|
let fun = url.startAccessingSecurityScopedResource()
|
||||||
let path = url.path
|
let path = url.path
|
||||||
@ -527,7 +546,6 @@ extension Game: Codable {
|
|||||||
// MARK: - Empty Library View
|
// MARK: - Empty Library View
|
||||||
struct EmptyGameLibraryView: View {
|
struct EmptyGameLibraryView: View {
|
||||||
@Binding var isSelectingGameFile: Bool
|
@Binding var isSelectingGameFile: Bool
|
||||||
@Binding var isImporting: Bool
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 24) {
|
VStack(spacing: 24) {
|
||||||
@ -550,7 +568,6 @@ struct EmptyGameLibraryView: View {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
isSelectingGameFile = true
|
isSelectingGameFile = true
|
||||||
isImporting = true
|
|
||||||
} label: {
|
} label: {
|
||||||
Label("Add Game", systemImage: "plus")
|
Label("Add Game", systemImage: "plus")
|
||||||
.font(.headline)
|
.font(.headline)
|
@ -58,6 +58,13 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
@AppStorage("checkForUpdate") var checkForUpdate: Bool = true
|
@AppStorage("checkForUpdate") var checkForUpdate: Bool = true
|
||||||
|
|
||||||
|
@AppStorage("disableTouch") var disableTouch = false
|
||||||
|
|
||||||
|
@AppStorage("runOnMainThread") var runOnMainThread = false
|
||||||
|
|
||||||
|
@AppCodableStorage("toggleButtons") var toggleButtons = ToggleButtonsState()
|
||||||
|
|
||||||
|
|
||||||
@State private var showResolutionInfo = false
|
@State private var showResolutionInfo = false
|
||||||
@State private var showAnisotropicInfo = false
|
@State private var showAnisotropicInfo = false
|
||||||
@State private var showControllerInfo = false
|
@State private var showControllerInfo = false
|
||||||
@ -480,6 +487,16 @@ struct SettingsView: View {
|
|||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
SettingsToggle(isOn: $swapBandA, icon: "rectangle.2.swap", label: "Swap Face Buttons (Physical Controller)")
|
SettingsToggle(isOn: $swapBandA, icon: "rectangle.2.swap", label: "Swap Face Buttons (Physical Controller)")
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
DisclosureGroup("Toggle Buttons") {
|
||||||
|
SettingsToggle(isOn: $toggleButtons.toggle1, icon: "circle.grid.cross.right.filled", label: "Toggle A")
|
||||||
|
SettingsToggle(isOn: $toggleButtons.toggle2, icon: "circle.grid.cross.down.filled", label: "Toggle B")
|
||||||
|
SettingsToggle(isOn: $toggleButtons.toggle3, icon: "circle.grid.cross.up.filled", label: "Toggle X")
|
||||||
|
SettingsToggle(isOn: $toggleButtons.toggle4, icon: "circle.grid.cross.left.filled", label: "Toggle Y")
|
||||||
|
}
|
||||||
|
.padding(.vertical, 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,6 +744,10 @@ struct SettingsView: View {
|
|||||||
// Advanced toggles card
|
// Advanced toggles card
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
|
SettingsToggle(isOn: $runOnMainThread, icon: "square.stack.3d.up", label: "Run Core on Main Thread")
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
SettingsToggle(isOn: $config.dfsIntegrityChecks, icon: "checkmark.shield", label: "Disable FS Integrity Checks")
|
SettingsToggle(isOn: $config.dfsIntegrityChecks, icon: "checkmark.shield", label: "Disable FS Integrity Checks")
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@ -837,8 +858,13 @@ struct SettingsView: View {
|
|||||||
SettingsSection(title: "Miscellaneous Options") {
|
SettingsSection(title: "Miscellaneous Options") {
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
|
// Disable Touch card
|
||||||
|
SettingsToggle(isOn: $disableTouch, icon: "rectangle.and.hand.point.up.left.filled", label: "Disable Touch")
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
// Screenshot button card
|
// Screenshot button card
|
||||||
SettingsToggle(isOn: $ssb, icon: "square.and.arrow.up", label: "Screenshot Button")
|
SettingsToggle(isOn: $ssb, icon: "arrow.left.circle", label: "Exit Button")
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
@ -34,10 +34,15 @@ struct MeloNXApp: App {
|
|||||||
@AppStorage("location-enabled") var locationenabled: Bool = false
|
@AppStorage("location-enabled") var locationenabled: Bool = false
|
||||||
@AppStorage("checkForUpdate") var checkForUpdate: Bool = true
|
@AppStorage("checkForUpdate") var checkForUpdate: Bool = true
|
||||||
|
|
||||||
|
@AppStorage("runOnMainThread") var runOnMainThread = false
|
||||||
|
|
||||||
|
@AppStorage("autoJIT") var autoJIT = false
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
if finishedStorage {
|
if finishedStorage {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
.withFileImporter()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if checkForUpdate {
|
if checkForUpdate {
|
||||||
checkLatestVersion()
|
checkLatestVersion()
|
||||||
@ -59,15 +64,13 @@ struct MeloNXApp: App {
|
|||||||
} else {
|
} else {
|
||||||
SetupView(finished: $finished)
|
SetupView(finished: $finished)
|
||||||
.onChange(of: finished) { newValue in
|
.onChange(of: finished) { newValue in
|
||||||
withAnimation {
|
withAnimation(.easeOut) {
|
||||||
withAnimation {
|
|
||||||
finishedStorage = newValue
|
finishedStorage = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func checkLatestVersion() {
|
func checkLatestVersion() {
|
||||||
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||||
|
@ -286,16 +286,6 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
{
|
{
|
||||||
_contentManager = new ContentManager(_virtualFileSystem);
|
_contentManager = new ContentManager(_virtualFileSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_accountManager == null)
|
|
||||||
{
|
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_userChannelPersistence == null)
|
|
||||||
{
|
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
@ -402,8 +392,8 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
return String.Empty;
|
return String.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
[UnmanagedCallersOnly(EntryPoint = "pause_emulation")]
|
||||||
public static void StopEmulation(bool shouldPause)
|
public static void PauseEmulation(bool shouldPause)
|
||||||
{
|
{
|
||||||
if (_window != null)
|
if (_window != null)
|
||||||
{
|
{
|
||||||
@ -422,6 +412,15 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "stop_emulation")]
|
||||||
|
public static void StopEmulation()
|
||||||
|
{
|
||||||
|
if (_window != null)
|
||||||
|
{
|
||||||
|
_window.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
|
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
|
||||||
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
|
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
|
||||||
{
|
{
|
||||||
@ -1133,43 +1132,23 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void Load(Options option)
|
static void Load(Options option)
|
||||||
{
|
|
||||||
|
|
||||||
if (_virtualFileSystem == null)
|
|
||||||
{
|
|
||||||
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_libHacHorizonManager == null)
|
|
||||||
{
|
{
|
||||||
_libHacHorizonManager = new LibHacHorizonManager();
|
_libHacHorizonManager = new LibHacHorizonManager();
|
||||||
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
|
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
|
||||||
_libHacHorizonManager.InitializeArpServer();
|
_libHacHorizonManager.InitializeArpServer();
|
||||||
_libHacHorizonManager.InitializeBcatServer();
|
_libHacHorizonManager.InitializeBcatServer();
|
||||||
_libHacHorizonManager.InitializeSystemClients();
|
_libHacHorizonManager.InitializeSystemClients();
|
||||||
}
|
|
||||||
|
|
||||||
if (_contentManager == null)
|
|
||||||
{
|
|
||||||
_contentManager = new ContentManager(_virtualFileSystem);
|
_contentManager = new ContentManager(_virtualFileSystem);
|
||||||
}
|
|
||||||
|
|
||||||
if (_accountManager == null)
|
|
||||||
{
|
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
|
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
|
||||||
}
|
|
||||||
|
|
||||||
if (_userChannelPersistence == null)
|
|
||||||
{
|
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
}
|
|
||||||
|
|
||||||
if (_inputManager == null)
|
|
||||||
{
|
|
||||||
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
||||||
}
|
|
||||||
|
|
||||||
if (OperatingSystem.IsIOS()) {
|
if (OperatingSystem.IsIOS())
|
||||||
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, $"Current Device: {option.DisplayName} ({option.DeviceModel}) {Environment.OSVersion.Version}");
|
Logger.Info?.Print(LogClass.Application, $"Current Device: {option.DisplayName} ({option.DeviceModel}) {Environment.OSVersion.Version}");
|
||||||
Logger.Info?.Print(LogClass.Application, $"Increased Memory Limit: {option.MemoryEnt}");
|
Logger.Info?.Print(LogClass.Application, $"Increased Memory Limit: {option.MemoryEnt}");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user